Add the last pages, and the plugin examples
This commit is contained in:
parent
d676c2ee7b
commit
f262d9f779
4 changed files with 225 additions and 4 deletions
67
doc/source/dev/contributing.rst
Normal file
67
doc/source/dev/contributing.rst
Normal file
|
@ -0,0 +1,67 @@
|
|||
Contributing
|
||||
============
|
||||
|
||||
Conventions
|
||||
-----------
|
||||
|
||||
We don’t have a strict set of conventions, but you should respect PEP8 mostly
|
||||
(e.g. 4 spaces, class names in CamelCase and methods lowercased with
|
||||
underscores) except if it means less-readable code (80 chars is often a hassle,
|
||||
and if you look inside poezio you’ll see lots of long lines, mostly because of
|
||||
strings).
|
||||
|
||||
As explained in the :ref:`overview`, “global” code goes in
|
||||
:file:`core.py`, tab-related code goes in :file:`tabs.py`, and ui-related code goes in
|
||||
:file:`windows.py`. There are other modules (e.g. :file:`xhtml.py`) but they do not matter
|
||||
for the application as a whole.
|
||||
|
||||
Commit guidelines
|
||||
-----------------
|
||||
|
||||
Commits **should** have a meaninful title (first line), and *may* have a detailed
|
||||
description below. There are of course exceptions (for example, a single-line
|
||||
commit that takes care of a typo right behind a big commit does not need to
|
||||
say ``fix a typo ("azre" → "are") in toto.py line 45454``, since the metainfos
|
||||
already take care of that.), but if you do not have commit access on the
|
||||
poezio trunk, you can still reset and commit again.
|
||||
|
||||
|
||||
Try to do atomic commits: since git is a DVCS, it doesn’t hurt to ``git add -p``
|
||||
and split the commit into several meaningful small commits ; on the contrary,
|
||||
it helps to track the changes on different levels.
|
||||
|
||||
|
||||
If you have a conflict, solve it with rebase and not merge if the fast-forwards
|
||||
do not resolve it automatically in your case. This helps to avoid creating
|
||||
useless merges (and polluting the commit history) when none is needed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git fetch origin
|
||||
git rebase origin/master
|
||||
git push origin master
|
||||
|
||||
If your commit is related to an issue on our tracker_ (or fixes such an
|
||||
issue), you can use ``Fix #BUGID`` or ``References #BUGID`` to help with the
|
||||
tracking.
|
||||
|
||||
|
||||
Getting your code into poezio
|
||||
-----------------------------
|
||||
|
||||
If you have code you want to contribute, you can:
|
||||
|
||||
* Give us a patch and a description of what it does
|
||||
* Give us a link to a **git** repo from which we can pull
|
||||
|
||||
The code is of course reviewed and tested a bit, but we trust the contributors
|
||||
to submit good code. If we can’t integrate the given code into poezio (if it
|
||||
crashes or has some issues), if the size is small, we may tweak it ourselves
|
||||
and integrate it, and if not, you are of course free to take our advice into
|
||||
account and submit it again.
|
||||
|
||||
|
||||
If you have already submitted some code and plan to do more, you can ask us
|
||||
direct commit access on the main repo.
|
||||
|
||||
.. _tracker: https://dev.louiz.org/project/poezio/bugs
|
|
@ -6,16 +6,25 @@
|
|||
Development documentation
|
||||
=========================
|
||||
|
||||
Contents:
|
||||
|
||||
About plugins
|
||||
-------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
plugin
|
||||
events
|
||||
timed_events
|
||||
common
|
||||
sleek
|
||||
theming
|
||||
xep
|
||||
|
||||
About Poezio
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
|
||||
overview
|
||||
contributing
|
||||
theming
|
||||
timed_events
|
||||
common
|
||||
|
|
114
doc/source/dev/overview.rst
Normal file
114
doc/source/dev/overview.rst
Normal file
|
@ -0,0 +1,114 @@
|
|||
.. _overview:
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
.. note:: This is not an introduction to XMPP, but to how poezio works.
|
||||
|
||||
|
||||
Global overview
|
||||
---------------
|
||||
|
||||
Poezio is an application that has three main layers, mostly separated in three
|
||||
different python modules: ``core``, ``tabs``, and ``windows``. An UML diagram of
|
||||
Poezio would be inneficient, cluttered, or incomplete, so there is none, if
|
||||
that bugs you.
|
||||
|
||||
.. figure:: ../images/layers.png
|
||||
:alt: Layers
|
||||
|
||||
**Core** is mostly a “global” object containing the state of the application at
|
||||
any time, it contains the global commands, the xmpp event handlers, the list
|
||||
of open tabs, etc. Most objects in poezio have a self.core attribute
|
||||
referencing the **Core** (it’s a singleton, so there is never more than one
|
||||
instance). **Core** also contains the main loop of the application, which then
|
||||
dispatchs the I/O events (keypress) to the appropriate methods.
|
||||
|
||||
But the main loop is not the most important thing in poezio; because it is an
|
||||
IM client, it is essentially event-driven. The event part is handled by
|
||||
SleekXMPP, which is the library we chose after moving away from xmpppy.
|
||||
|
||||
|
||||
**Tabs** are the second layer of poezio, but the first dealing with the UI: each
|
||||
**Tab** is a layout of several **windows**, it contains tab-specific commands,
|
||||
tab-specific keybinds, and it has methods in order for core to
|
||||
interact with it, and some methods are only proxies for the methods of a
|
||||
**window**.
|
||||
|
||||
Example scenario: If someone presses the key PageUp, then Core will call the
|
||||
appropriate method on the current _Tab_, which will in turn, if it implements the
|
||||
method (inherited empty from the Tab class), call a scrolling method from the
|
||||
appropriate **window**.
|
||||
|
||||
All tabs types inherit from the class **Tab**, and the tabs featuring
|
||||
chat functionnality will inherit from **ChatTab** (which inherits from **Tab**).
|
||||
|
||||
Examples of **tabs**: MUCTab, XMLTab, RosterTab, MUCListTab, etc…
|
||||
|
||||
Event handlers
|
||||
--------------
|
||||
|
||||
The events handlers are registered right at the start of poezio, and then
|
||||
when a matching stanza is received, the handler is called in a separate thread
|
||||
from the main loop. The handlers are in **Core**, and then they call the
|
||||
appropriate methods in the corresponding **tabs**.
|
||||
|
||||
Example scenario: if a message is received from a MUC, then the **Core** handler
|
||||
will identify the **Tab**, and call the relevant handler from this **Tab**, this tab
|
||||
will in turn, add the message to the buffer, which will then add it to the
|
||||
relevant **windows**.
|
||||
|
||||
.. note:: All the _windows_ that deal with received or generated text are linked
|
||||
to a **text_buffer**, in order to rebuild all the display lines from the
|
||||
sources if necessary. This also enables us to have several **windows**
|
||||
presenting the same text, even if they are not of the same size and layout.
|
||||
|
||||
Commands and completion
|
||||
-----------------------
|
||||
|
||||
Commands are quite straightforward: those are methods that take a string as a
|
||||
parameter, and they do stuff.
|
||||
|
||||
From an user point of view, the methods are entered like that:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/command arg1 arg2
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
/command "arg1 with spaces" arg2
|
||||
|
||||
However, when creating a command, you wil deal with _one_ str, no matter what.
|
||||
There are utilities to deal with it (common.shell_split), but it is not always
|
||||
necessary. Commands are registered in the **commands** dictionnary of a tab
|
||||
structured as key (command name) -> tuple(command function, help string, completion).
|
||||
|
||||
|
||||
Completions are a bit tricky, but it’s easy once you get used to it:
|
||||
|
||||
They take an **Input** (a _windows_ class) as a parameter, named the_input
|
||||
everywhere in the sources. To effectively have a completion, you have to call
|
||||
**the_input.auto_completion()** at the end of the function.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Input(Win):
|
||||
# …
|
||||
def auto_completion(completion_list, after='', quote=True):
|
||||
# …
|
||||
|
||||
Set the input to iterate over _completion_list_ when the user hits tab, insert
|
||||
**after** after the completed item, and surround the item with double quotes or
|
||||
not.
|
||||
|
||||
There is no method to find the current argument in the input (although the
|
||||
feature is planned), so you have to assume the current argument is the last,
|
||||
and guess it by splitting the string an checking for end-space.
|
||||
|
||||
You can look for examples in the sources, all the possible cases are
|
||||
covered (single-argument, complex arguments with spaces, several arguments,
|
||||
etc…)
|
|
@ -1,6 +1,9 @@
|
|||
Plugin API documentation
|
||||
========================
|
||||
|
||||
BasePlugin
|
||||
----------
|
||||
|
||||
.. module:: plugin
|
||||
|
||||
.. autoclass:: BasePlugin
|
||||
|
@ -34,8 +37,36 @@ The :py:class:`~PluginAPI` object is an a interface through which the :py:class:
|
|||
(and inheritors) *should* go to interact with poezio. If it is not sufficient, then the ``core``
|
||||
member can be used.
|
||||
|
||||
PluginAPI
|
||||
---------
|
||||
|
||||
.. autoclass:: PluginAPI
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Example plugins
|
||||
---------------
|
||||
|
||||
**Example 1:** Add a simple command that sends "Hello World!" into the conversation
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.add_command('hello', self.command_hello, "Send 'Hello World!'")
|
||||
|
||||
def command_hello(self, arg):
|
||||
self.core.send_message('Hello World!')
|
||||
|
||||
**Example 2:** Adds an event handler that sends “tg” to a groupchat when a message is received from someone named “Partauche”
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
def init(self):
|
||||
self.add_event_handler('muc_msg', self.on_groupchat_message)
|
||||
|
||||
def on_groupchat_message(self, message, tab):
|
||||
if message['mucnick'] == "Partauche":
|
||||
tab.command_say('tg')
|
||||
|
|
Loading…
Reference in a new issue