Skip to content

Commit 0aa9eb0

Browse files
committed
HTML documentation
1 parent 9fccc9b commit 0aa9eb0

File tree

2 files changed

+167
-1
lines changed

2 files changed

+167
-1
lines changed

README.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ users can probably support a HTML file, which can be rendered with:
7373

7474
.. code-block:: console
7575
76-
$ docutils click_plugins.rst click_plugins.html
76+
$ docutils \
77+
click_plugins.rst \
78+
click_plugins.html \
79+
--date \
80+
--leave-comments \
81+
--no-generator \
82+
--no-source-link \
83+
--stylesheet-path "" \
84+
--root-prefix ./
7785
7886
7987
Release

click_plugins.html

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="generator" content="Docutils 0.21.2: https://docutils.sourceforge.io/" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<title>click-plugins</title>
8+
9+
</head>
10+
<body class="with-toc">
11+
<main id="click-plugins">
12+
<h1 class="title"><span class="docutils literal"><span class="pre">click-plugins</span></span></h1>
13+
14+
<!-- This file is part of 'click-plugins' version 2.0: https://github.com/click-contrib/click-plugins -->
15+
<p>Load <a class="reference external" href="https://click.palletsprojects.com/">click</a> commands from
16+
<a class="reference external" href="https://docs.python.org/3/library/importlib.metadata.html#entry-points">entry points</a>.
17+
Allows a click-based command line interface to load commands from external
18+
packages.</p>
19+
<nav class="contents" id="table-of-contents" role="doc-toc">
20+
<p class="topic-title"><a class="reference internal" href="#top">Table of Contents</a></p>
21+
<ul class="simple">
22+
<li><p><a class="reference internal" href="#what-is-a-plugin" id="toc-entry-1">What is a plugin?</a></p></li>
23+
<li><p><a class="reference internal" href="#why-would-i-want-a-plugin" id="toc-entry-2">Why would I want a plugin?</a></p></li>
24+
<li><p><a class="reference internal" href="#i-am-a-developer-wanting-to-support-plugins-on-my-cli" id="toc-entry-3">I am a developer wanting to support plugins on my CLI</a></p>
25+
<ul>
26+
<li><p><a class="reference internal" href="#support" id="toc-entry-4">Support</a></p></li>
27+
</ul>
28+
</li>
29+
<li><p><a class="reference internal" href="#i-am-a-plugin-author" id="toc-entry-5">I am a plugin author</a></p></li>
30+
<li><p><a class="reference internal" href="#license" id="toc-entry-6">License</a></p></li>
31+
</ul>
32+
</nav>
33+
<section id="what-is-a-plugin">
34+
<h2><a class="toc-backref" href="#toc-entry-1" role="doc-backlink">What is a plugin?</a></h2>
35+
<p>A plugin is similar to installing and importing a Python package, except the
36+
code conforms to a specific protocol, and is loaded through other means.</p>
37+
</section>
38+
<section id="why-would-i-want-a-plugin">
39+
<h2><a class="toc-backref" href="#toc-entry-2" role="doc-backlink">Why would I want a plugin?</a></h2>
40+
<p>Library developers providing a command line interface can load plugins to
41+
extend its features by allowing other developers to register additional
42+
commands or groups. This allows for an extensible command line, and a natural
43+
home for commands that aren't a great fit for the primary CLI, but belong in
44+
the broader ecosystem. For example, a plugin might provide a more advanced set
45+
of features, but require additional dependencies.</p>
46+
</section>
47+
<section id="i-am-a-developer-wanting-to-support-plugins-on-my-cli">
48+
<h2><a class="toc-backref" href="#toc-entry-3" role="doc-backlink">I am a developer wanting to support plugins on my CLI</a></h2>
49+
<p>A <a class="reference external" href="https://pypi.org/project/click-plugins/">click-plugins</a> package exists on
50+
the Python Package Index. This is an older version that is no longer supported
51+
and will not be updated. Instead, developers should vendor
52+
<span class="docutils literal">click_plugins.py</span>, and consider vendoring <span class="docutils literal">click_plugins_tests.py</span>, and
53+
<span class="docutils literal">click_plugins.rst</span>. Alternatively, developers are free to use this project
54+
as a reference for their own implementation, or make modifications in
55+
accordance with the license.</p>
56+
<p>Some considerations for vendoring are speed, and packaging. Entrypoints are
57+
known to be slow to load, and some alternative approaches exist at the cost of
58+
additional dependencies, or assumptions about what happens when a plugin fails
59+
to load. Vendoring <span class="docutils literal"><span class="pre">click-plugins</span></span> might include changing the entry point
60+
loading mechanism to one that is more appropriate for your use. Python
61+
packaging can be quite complicated in some cases, and vendoring may require
62+
adjustments for your specific packaging setup.</p>
63+
<p>In order to support loading plugins, developers must document where their
64+
library is looking for entry points. Exactly how to do this varies based on
65+
packaging tooling, but it is supported by <a class="reference external" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools</a>.
66+
A project may offer several entry points allowing plugins to choose where they
67+
are registered in the CLI. Including the package name in the entrypoint is
68+
good, so an example might look like <span class="docutils literal">package.plugins</span> or
69+
<span class="docutils literal">package.subcommand.plugins</span>. If <span class="docutils literal"><span class="pre">click-plugins</span></span> offered plugins, it might
70+
want to register them at <span class="docutils literal">click_plugins.plugins</span>.</p>
71+
<p>This entry point should be associated with a <span class="docutils literal">click.Group()</span> where the
72+
plugins will live:</p>
73+
<pre class="code python literal-block"><code><span class="keyword namespace">from</span><span class="whitespace"> </span><span class="name namespace">click</span><span class="whitespace">
74+
</span><span class="keyword namespace">from</span><span class="whitespace"> </span><span class="name namespace">click_plugins</span><span class="whitespace"> </span><span class="keyword namespace">import</span> <span class="name">with_plugins</span><span class="whitespace">
75+
76+
</span><span class="name decorator">&#64;with_plugins</span><span class="punctuation">(</span><span class="literal string single">'example.entry.point'</span><span class="punctuation">)</span><span class="whitespace">
77+
</span><span class="name decorator">&#64;click</span><span class="operator">.</span><span class="name">group</span><span class="punctuation">(</span><span class="literal string single">'External plugins'</span><span class="punctuation">)</span><span class="whitespace">
78+
</span><span class="keyword">def</span><span class="whitespace"> </span><span class="name function">group</span><span class="punctuation">():</span><span class="whitespace">
79+
</span> <span class="operator">...</span></code></pre>
80+
<p><span class="docutils literal">click_plugins.with_plugins()</span> has a docstring describing alternate
81+
invocations.</p>
82+
<p>Some developers use <span class="docutils literal"><span class="pre">click-plugins</span></span> as an easy way to assemble the CLI for
83+
their project in addition to supporting plugins. This approach does work, but
84+
can cause CLI startup to be slow. Developers taking this approach might
85+
consider entry point for the primary CLI, and one for plugins.</p>
86+
<p>Packages offering plugins of the same name will experience collisions.</p>
87+
<section id="support">
88+
<h3><a class="toc-backref" href="#toc-entry-4" role="doc-backlink">Support</a></h3>
89+
<p>Offering a home for plugins comes with a certain amount of support. The primary
90+
CLI author is likely to sometimes receive bug reports or feature requests for
91+
plugins that are not part of the core project. <span class="docutils literal"><span class="pre">click-plugins</span></span> attempts to
92+
gracefully handle plugins that fail to load, and nudges the user towards the
93+
plugin author, but the plugin origin may at times not be clear. Consider that
94+
your users are primarily interacting with your CLI, but may be experiencing
95+
problems with a plugin, or even a bad interaction between plugins. It may be
96+
worth including a brief description about this in your documentation to help
97+
users report issues to the correct location.</p>
98+
</section>
99+
</section>
100+
<section id="i-am-a-plugin-author">
101+
<h2><a class="toc-backref" href="#toc-entry-5" role="doc-backlink">I am a plugin author</a></h2>
102+
<p>Register your <span class="docutils literal">click.Command()</span> or <span class="docutils literal">click.Group()</span> as an
103+
<a class="reference external" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">entry point</a>.
104+
The exact mechanism depends on your packaging choices, but for a
105+
<span class="docutils literal">pyproject.toml</span> with <span class="docutils literal">setuptools</span> as a backend, it looks like:</p>
106+
<pre class="code toml literal-block"><code><span class="keyword">[tool.setuptools.dynamic]</span><span class="whitespace">
107+
</span><span class="name">entry-points</span><span class="whitespace"> </span><span class="operator">=</span><span class="whitespace">
108+
</span><span class="error">name = library.submodule:object</span></code></pre>
109+
<p>If <span class="docutils literal">click_plugins</span> had a <span class="docutils literal">plugins.py</span> submodule, it might contain a
110+
plugin structured as the <span class="docutils literal">click.Command()</span> below:</p>
111+
<pre class="code python literal-block"><code><span class="keyword namespace">import</span><span class="whitespace"> </span><span class="name namespace">click</span><span class="whitespace">
112+
113+
</span><span class="name decorator">&#64;click</span><span class="operator">.</span><span class="name">command</span><span class="punctuation">(</span><span class="literal string single">'uppercase'</span><span class="punctuation">)</span><span class="whitespace">
114+
</span><span class="keyword">def</span><span class="whitespace"> </span><span class="name function">uppercase</span><span class="punctuation">():</span><span class="whitespace">
115+
</span><span class="literal string doc">&quot;&quot;&quot;Echo stdin in uppercase.&quot;&quot;&quot;</span><span class="whitespace">
116+
</span> <span class="keyword">with</span> <span class="name">click</span><span class="operator">.</span><span class="name">get_text_stream</span><span class="punctuation">(</span><span class="literal string single">'stdin'</span><span class="punctuation">)</span> <span class="keyword">as</span> <span class="name">f</span><span class="punctuation">:</span><span class="whitespace">
117+
</span> <span class="keyword">for</span> <span class="name">line</span> <span class="operator word">in</span> <span class="name">f</span><span class="punctuation">:</span><span class="whitespace">
118+
</span> <span class="name">click</span><span class="operator">.</span><span class="name">echo</span><span class="punctuation">(</span><span class="name">f</span><span class="operator">.</span><span class="name">upper</span><span class="punctuation">())</span></code></pre>
119+
<p>This would be attached to an entry point like:</p>
120+
<pre class="code toml literal-block"><code><span class="keyword">[tool.setuptools.dynamic]</span><span class="whitespace">
121+
</span><span class="name">entry-points</span><span class="whitespace"> </span><span class="operator">=</span><span class="whitespace">
122+
</span><span class="error">bold = click</span><span class="literal number integer">_</span><span class="name">plugins</span><span class="punctuation">.</span><span class="name">plugins</span><span class="error">:</span><span class="name">bold</span></code></pre>
123+
</section>
124+
<section id="license">
125+
<h2><a class="toc-backref" href="#toc-entry-6" role="doc-backlink">License</a></h2>
126+
<p>New BSD License</p>
127+
<p>Copyright (c) 2015-2025, Kevin D. Wurster, Sean C. Gillies
128+
All rights reserved.</p>
129+
<p>Redistribution and use in source and binary forms, with or without
130+
modification, are permitted provided that the following conditions are met:</p>
131+
<ul class="simple">
132+
<li><p>Redistributions of source code must retain the above copyright notice, this
133+
list of conditions and the following disclaimer.</p></li>
134+
<li><p>Redistributions in binary form must reproduce the above copyright notice,
135+
this list of conditions and the following disclaimer in the documentation
136+
and/or other materials provided with the distribution.</p></li>
137+
<li><p>Neither click-plugins nor the names of its contributors may not be used to
138+
endorse or promote products derived from this software without specific prior
139+
written permission.</p></li>
140+
</ul>
141+
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot;
142+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
143+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
144+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
145+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
146+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
147+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
148+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
149+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
150+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
151+
</section>
152+
</main>
153+
<footer>
154+
<p>Generated on: 2025-06-09.
155+
</p>
156+
</footer>
157+
</body>
158+
</html>

0 commit comments

Comments
 (0)