Break the docs out into their own branch.

This commit is contained in:
Lance Stout 2012-01-05 11:31:54 -05:00
commit 97378998a5
56 changed files with 5809 additions and 0 deletions

130
docs/Makefile Normal file
View file

@ -0,0 +1,130 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SleekXMPP.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SleekXMPP.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/SleekXMPP"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SleekXMPP"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

452
docs/_static/agogo.css vendored Normal file
View file

@ -0,0 +1,452 @@
/*
* agogo.css_t
* ~~~~~~~~~~~
*
* Sphinx stylesheet -- agogo theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
* {
margin: 0px;
padding: 0px;
}
body {
font-family: "Verdana", Arial, sans-serif;
line-height: 1.4em;
color: black;
background-color: #eeeeec;
}
/* Page layout */
div.header, div.content, div.footer {
width: 70em;
margin-left: auto;
margin-right: auto;
}
div.header-wrapper {
background: url(bgtop.png) top left repeat-x;
border-bottom: 3px solid #2e3436;
}
/* Default body styles */
a {
color: #ce5c00;
}
div.bodywrapper a, div.footer a {
text-decoration: underline;
}
.clearer {
clear: both;
}
.left {
float: left;
}
.right {
float: right;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
h1, h2, h3, h4 {
font-family: "Georgia", "Times New Roman", serif;
font-weight: normal;
color: #3465a4;
margin-bottom: .8em;
}
h1 {
color: #204a87;
}
h2 {
padding-bottom: .5em;
border-bottom: 1px solid #3465a4;
}
a.headerlink {
visibility: hidden;
color: #dddddd;
padding-left: .3em;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
img {
border: 0;
}
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 2px 7px 1px 7px;
border-left: 0.2em solid black;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
dt:target, .highlighted {
background-color: #fbe54e;
}
/* Header */
div.header {
padding-top: 10px;
padding-bottom: 10px;
}
div.header h1 {
font-family: "Georgia", "Times New Roman", serif;
font-weight: normal;
font-size: 180%;
letter-spacing: .08em;
}
div.header h1 a {
color: white;
}
div.header div.rel {
margin-top: 1em;
}
div.header div.rel a {
color: #fcaf3e;
letter-spacing: .1em;
text-transform: uppercase;
}
p.logo {
float: right;
}
img.logo {
border: 0;
}
/* Content */
div.content-wrapper {
background-color: white;
padding-top: 20px;
padding-bottom: 20px;
}
div.document {
width: 50em;
float: left;
}
div.body {
padding-right: 2em;
text-align: justify;
}
div.document ul {
margin: 1.5em;
list-style-type: square;
}
div.document dd {
margin-left: 1.2em;
margin-top: .4em;
margin-bottom: 1em;
}
div.document .section {
margin-top: 1.7em;
}
div.document .section:first-child {
margin-top: 0px;
}
div.document div.highlight {
padding: 3px;
background-color: #eeeeec;
border-top: 2px solid #dddddd;
border-bottom: 2px solid #dddddd;
margin-top: .8em;
margin-bottom: .8em;
}
div.document h2 {
margin-top: .7em;
}
div.document p {
margin-bottom: .5em;
}
div.document li.toctree-l1 {
margin-bottom: 1em;
}
div.document .descname {
font-weight: bold;
}
div.document .docutils.literal {
background-color: #eeeeec;
padding: 1px;
}
div.document .docutils.xref.literal {
background-color: transparent;
padding: 0px;
}
div.document blockquote {
margin: 1em;
}
div.document ol {
margin: 1.5em;
}
/* Sidebar */
div.sidebar {
width: 20em;
float: right;
font-size: .9em;
}
div.sidebar a, div.header a {
text-decoration: none;
}
div.sidebar a:hover, div.header a:hover {
text-decoration: underline;
}
div.sidebar h3 {
color: #2e3436;
text-transform: uppercase;
font-size: 130%;
letter-spacing: .1em;
}
div.sidebar ul {
list-style-type: none;
}
div.sidebar li.toctree-l1 a {
display: block;
padding: 1px;
border: 1px solid #dddddd;
background-color: #eeeeec;
margin-bottom: .4em;
padding-left: 3px;
color: #2e3436;
}
div.sidebar li.toctree-l2 a {
background-color: transparent;
border: none;
margin-left: 1em;
border-bottom: 1px solid #dddddd;
}
div.sidebar li.toctree-l3 a {
background-color: transparent;
border: none;
margin-left: 2em;
border-bottom: 1px solid #dddddd;
}
div.sidebar li.toctree-l2:last-child a {
border-bottom: none;
}
div.sidebar li.toctree-l1.current a {
border-right: 5px solid #fcaf3e;
}
div.sidebar li.toctree-l1.current li.toctree-l2 a {
border-right: none;
}
/* Footer */
div.footer-wrapper {
background: url(bgfooter.png) top left repeat-x;
border-top: 4px solid #babdb6;
padding-top: 10px;
padding-bottom: 10px;
min-height: 80px;
}
div.footer, div.footer a {
color: #888a85;
}
div.footer .right {
text-align: right;
}
div.footer .left {
text-transform: uppercase;
}
/* Styles copied from basic theme */
img.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
clear: both;
text-align: center;
}
.align-right {
text-align: right;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
/* -- viewcode extension ---------------------------------------------------- */
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family:: "Verdana", Arial, sans-serif;
}
div.viewcode-block:target {
margin: -1px -3px;
padding: 0 3px;
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

532
docs/_static/basic.css vendored Normal file
View file

@ -0,0 +1,532 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
img {
border: 0;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- general body styles --------------------------------------------------- */
a.headerlink {
visibility: hidden;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
div.body p.caption {
text-align: inherit;
}
div.body td {
text-align: left;
}
.field-list ul {
padding-left: 1em;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
img.align-left, .figure.align-left, object.align-left {
clear: left;
float: left;
margin-right: 1em;
}
img.align-right, .figure.align-right, object.align-right {
clear: right;
float: right;
margin-left: 1em;
}
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left;
}
.align-center {
clear: both;
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #efefef;
width: 40%;
float: right;
-mox-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
p.sidebar-title {
font-weight: bold;
text-transform: uppercase;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.body p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.field-list td, table.field-list th {
border: 0 !important;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
dl {
margin-bottom: 15px;
}
dd p {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, .highlighted {
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.refcount {
color: #060;
}
.optional {
font-size: 1.3em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa;
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
overflow-y: hidden; /* fixes display issues on Chrome browsers */
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
background-color: transparent;
}
tt.xref, a tt {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.body div.math p {
text-align: center;
}
span.eqno {
float: right;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

256
docs/_static/default.css vendored Normal file
View file

@ -0,0 +1,256 @@
/*
* default.css_t
* ~~~~~~~~~~~~~
*
* Sphinx stylesheet -- default theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: sans-serif;
font-size: 100%;
background-color: #11303d;
color: #000;
margin: 0;
padding: 0;
}
div.document {
background-color: #1c4e63;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
div.body {
background-color: #ffffff;
color: #000000;
padding: 0 20px 30px 20px;
}
div.footer {
color: #ffffff;
width: 100%;
padding: 9px 0 9px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #ffffff;
text-decoration: underline;
}
div.related {
background-color: #133f52;
line-height: 30px;
color: #ffffff;
}
div.related a {
color: #ffffff;
}
div.sphinxsidebar {
}
div.sphinxsidebar h3 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.4em;
font-weight: normal;
margin: 0;
padding: 0;
}
div.sphinxsidebar h3 a {
color: #ffffff;
}
div.sphinxsidebar h4 {
font-family: 'Trebuchet MS', sans-serif;
color: #ffffff;
font-size: 1.3em;
font-weight: normal;
margin: 5px 0 0 0;
padding: 0;
}
div.sphinxsidebar p {
color: #ffffff;
}
div.sphinxsidebar p.topless {
margin: 5px 10px 10px 10px;
}
div.sphinxsidebar ul {
margin: 10px;
padding: 0;
color: #ffffff;
}
div.sphinxsidebar a {
color: #98dbcc;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
/* -- hyperlink styles ------------------------------------------------------ */
a {
color: #355f7c;
text-decoration: none;
}
a:visited {
color: #355f7c;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: 'Trebuchet MS', sans-serif;
background-color: #f2f2f2;
font-weight: normal;
color: #20435c;
border-bottom: 1px solid #ccc;
margin: 20px -20px 10px -20px;
padding: 3px 0 3px 10px;
}
div.body h1 { margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 160%; }
div.body h3 { font-size: 140%; }
div.body h4 { font-size: 120%; }
div.body h5 { font-size: 110%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
text-align: justify;
line-height: 130%;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.admonition p {
margin-bottom: 5px;
}
div.admonition pre {
margin-bottom: 5px;
}
div.admonition ul, div.admonition ol {
margin-bottom: 5px;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 5px;
background-color: #eeffcc;
color: #333333;
line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
tt {
background-color: #ecf0f3;
padding: 0 1px 0 1px;
font-size: 0.95em;
}
th {
background-color: #ede;
}
.warning tt {
background: #efc2c2;
}
.note tt {
background: #d6d6d6;
}
.viewcode-back {
font-family: sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

BIN
docs/_static/fonts/Museo_Slab_500.otf vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
docs/_static/fonts/OFLGoudyStMTT.ttf vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

406
docs/_static/haiku.css vendored Normal file
View file

@ -0,0 +1,406 @@
/*
* haiku.css_t
* ~~~~~~~~~~~
*
* Sphinx stylesheet -- haiku theme.
*
* Adapted from http://haiku-os.org/docs/Haiku-doc.css.
* Original copyright message:
*
* Copyright 2008-2009, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Francois Revol <revol@free.fr>
* Stephan Assmus <superstippi@gmx.de>
* Braden Ewing <brewin@gmail.com>
* Humdinger <humdingerb@gmail.com>
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
@font-face {
font-family: "Museo Slab";
font-weight: normal;
font-style: normal;
src: local("Museo Slab"),
url("fonts/Museo_Slab_500.otf") format("opentype");
}
@font-face {
font-family: "Yanone Kaffeesatz";
font-weight: bold;
font-style: normal;
src: local("Yanone Kaffeesatz"),
url("fonts/YanoneKaffeesatz-Bold.ttf") format("truetype");
}
@font-face {
font-family: "Yanone Kaffeesatz";
font-weight: lighter;
font-style: normal;
src: local("Yanone Kaffeesatz"),
url("fonts/YanoneKaffeesatz-Regular.ttf") format("truetype");
}
html {
margin: 0px;
padding: 0px;
background: #FFF url(header.png) top left repeat-x;
}
body {
line-height: 1.5;
margin: auto;
padding: 0px;
font-family: "Helvetica Neueu", Helvetica, sans-serif;
min-width: 59em;
max-width: 70em;
color: #444;
}
div.footer {
padding: 8px;
font-size: 11px;
text-align: center;
letter-spacing: 0.5px;
}
/* link colors and text decoration */
a:link {
font-weight: bold;
text-decoration: none;
color: #00ADEE;
}
a:visited {
font-weight: bold;
text-decoration: none;
color: #00ADEE;
}
a:hover, a:active {
text-decoration: underline;
color: #F46DBA;
}
/* Some headers act as anchors, don't give them a hover effect */
h1 a:hover, a:active {
text-decoration: none;
color: #CFCFCF;
}
h2 a:hover, a:active {
text-decoration: none;
color: #CFCFCF;
}
h3 a:hover, a:active {
text-decoration: none;
color: #CFCFCF;
}
h4 a:hover, a:active {
text-decoration: none;
color: #CFCFCF;
}
a.headerlink {
color: #a7ce38;
padding-left: 5px;
}
a.headerlink:hover {
color: #a7ce38;
}
/* basic text elements */
div.content {
margin-top: 20px;
margin-left: 40px;
margin-right: 40px;
margin-bottom: 50px;
font-size: 0.9em;
}
/* heading and navigation */
div.header {
position: relative;
margin-top: 125px;
height: 85px;
padding: 0 40px;
font-family: "Yanone Kaffeesatz";
}
div.header h1 {
font-size: 2.6em;
font-weight: normal;
letter-spacing: 1px;
color: #CFCFCF;
border: 0;
margin: 0;
padding-top: 15px;
font-family: "Yanone Kaffeesatz";
text-shadow: 1px 1px 1px rgba(175, 175, 175, .8);
font-variant: small-caps;
}
div.header h1 a {
font-weight: normal;
color: #00ADEE;
}
div.header h2 {
font-size: 1.3em;
font-weight: normal;
letter-spacing: 1px;
text-transform: uppercase;
color: #aaa;
border: 0;
margin-top: -3px;
padding: 0;
font-family: "Yanone Kaffeesatz";
}
div.header img.rightlogo {
float: right;
}
div.title {
font-size: 1.3em;
font-weight: bold;
color: #CFCFCF;
border-bottom: dotted thin #e0e0e0;
margin-bottom: 25px;
}
div.topnav {
position: relative;
z-index: 0;
}
div.topnav p {
margin-top: 0;
margin-left: 40px;
margin-right: 40px;
margin-bottom: 0px;
text-align: right;
font-size: 0.8em;
}
div.bottomnav {
background: #eeeeee;
}
div.bottomnav p {
margin-right: 40px;
text-align: right;
font-size: 0.8em;
}
a.uplink {
font-weight: normal;
}
/* contents box */
table.index {
margin: 0px 0px 30px 30px;
padding: 1px;
border-width: 1px;
border-style: dotted;
border-color: #e0e0e0;
}
table.index tr.heading {
background-color: #e0e0e0;
text-align: center;
font-weight: bold;
font-size: 1.1em;
}
table.index tr.index {
background-color: #eeeeee;
}
table.index td {
padding: 5px 20px;
}
table.index a:link, table.index a:visited {
font-weight: normal;
text-decoration: none;
color: #4A7389;
}
table.index a:hover, table.index a:active {
text-decoration: underline;
color: #ff4500;
}
/* Haiku User Guide styles and layout */
/* Rounded corner boxes */
/* Common declarations */
div.admonition {
-webkit-border-radius: 10px;
-khtml-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
border-style: dotted;
border-width: thin;
border-color: #dcdcdc;
padding: 10px 15px 10px 15px;
margin-bottom: 15px;
margin-top: 15px;
}
div.note {
padding: 10px 15px 10px 15px;
background-color: #e4ffde;
/*background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;*/
min-height: 42px;
}
div.warning {
padding: 10px 15px 10px 15px;
background-color: #fffbc6;
/*background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;*/
min-height: 42px;
}
div.seealso {
background: #e4ffde;
}
/* More layout and styles */
h1 {
font-size: 1.6em;
color: #aaa;
border-bottom: dotted thin #e0e0e0;
margin-top: 30px;
font-family: "Museo Slab";
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
}
h2 {
font-size: 1.5em;
font-weight: normal;
color: #aaa;
border-bottom: dotted thin #e0e0e0;
margin-top: 30px;
font-family: "Museo Slab";
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
}
h3 {
font-size: 1.4em;
font-weight: normal;
color: #aaa;
margin-top: 30px;
font-family: "Museo Slab";
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
}
h4 {
font-size: 1.3em;
font-weight: normal;
color: #CFCFCF;
margin-top: 30px;
}
p {
text-align: justify;
}
p.last {
margin-bottom: 0;
}
ol {
padding-left: 20px;
}
ul {
padding-left: 5px;
margin-top: 3px;
}
li {
line-height: 1.3;
}
div.content ul > li {
-moz-background-clip:border;
-moz-background-inline-policy:continuous;
-moz-background-origin:padding;
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
list-style-image: none;
list-style-type: none;
padding: 0 0 0 1.666em;
margin-bottom: 3px;
}
td {
vertical-align: top;
}
tt {
background-color: #e2e2e2;
font-size: 1.0em;
font-family: monospace;
}
pre {
font-size: 1.1em;
margin: 0 0 12px 0;
padding: 0.8em;
background-image: url(noise_dk.png);
background-color: #222;
}
hr {
border-top: 1px solid #ccc;
border-bottom: 0;
border-right: 0;
border-left: 0;
margin-bottom: 10px;
margin-top: 20px;
}
/* printer only pretty stuff */
@media print {
.noprint {
display: none;
}
/* for acronyms we want their definitions inlined at print time */
acronym[title]:after {
font-size: small;
content: " (" attr(title) ")";
font-style: italic;
}
/* and not have mozilla dotted underline */
acronym {
border: none;
}
div.topnav, div.bottomnav, div.header, table.index {
display: none;
}
div.content {
margin: 0px;
padding: 0px;
}
html {
background: #FFF;
}
}
.viewcode-back {
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
}
div.viewcode-block:target {
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
margin: -1px -12px;
padding: 0 12px;
}

BIN
docs/_static/header.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/_static/images/arch_layers.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

70
docs/_static/ir_black.css vendored Normal file
View file

@ -0,0 +1,70 @@
.highlight .hll { background-color: #ffffcc }
.highlight { background: #000000; color: #f6f3e8; }
.highlight .c { color: #7C7C7C; } /* Comment */
.highlight .err { color: #f6f3e8; } /* Error */
.highlight .g { color: #f6f3e8; } /* Generic */
.highlight .k { color: #00ADEE; } /* Keyword */
.highlight .l { color: #f6f3e8; } /* Literal */
.highlight .n { color: #f6f3e8; } /* Name */
.highlight .o { color: #f6f3e8; } /* Operator */
.highlight .x { color: #f6f3e8; } /* Other */
.highlight .p { color: #f6f3e8; } /* Punctuation */
.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
.highlight .cs { color: #7C7C7C; } /* Comment.Special */
.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
.highlight .go { color: #070707; } /* Generic.Output */
.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
.highlight .kc { color: #6699CC; } /* Keyword.Constant */
.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
.highlight .ld { color: #f6f3e8; } /* Literal.Date */
.highlight .m { color: #FF73FD; } /* Literal.Number */
.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
.highlight .na { color: #f6f3e8; } /* Name.Attribute */
.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
.highlight .nc { color: #f6f3e8; } /* Name.Class */
.highlight .no { color: #99CC99; } /* Name.Constant */
.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
.highlight .ni { color: #E18964; } /* Name.Entity */
.highlight .ne { color: #f6f3e8; } /* Name.Exception */
.highlight .nf { color: #F64DBA; } /* Name.Function */
.highlight .nl { color: #f6f3e8; } /* Name.Label */
.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
.highlight .nx { color: #f6f3e8; } /* Name.Other */
.highlight .py { color: #f6f3e8; } /* Name.Property */
.highlight .nt { color: #00ADEE; } /* Name.Tag */
.highlight .nv { color: #C6C5FE; } /* Name.Variable */
.highlight .ow { color: #ffffff; } /* Operator.Word */
.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */

245
docs/_static/nature.css vendored Normal file
View file

@ -0,0 +1,245 @@
/*
* nature.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- nature theme.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: Arial, sans-serif;
font-size: 100%;
background-color: #111;
color: #555;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
hr {
border: 1px solid #B1B4B6;
}
div.document {
background-color: #eee;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
font-size: 0.9em;
}
div.footer {
color: #555;
width: 100%;
padding: 13px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #444;
text-decoration: underline;
}
div.related {
background-color: #6BA81E;
line-height: 32px;
color: #fff;
text-shadow: 0px 1px 0 #444;
font-size: 0.9em;
}
div.related a {
color: #E2F3CC;
}
div.sphinxsidebar {
font-size: 0.75em;
line-height: 1.5em;
}
div.sphinxsidebarwrapper{
padding: 20px 0;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: Arial, sans-serif;
color: #222;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 5px 10px;
background-color: #ddd;
text-shadow: 1px 1px 0 white
}
div.sphinxsidebar h4{
font-size: 1.1em;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p {
color: #888;
padding: 5px 20px;
}
div.sphinxsidebar p.topless {
}
div.sphinxsidebar ul {
margin: 10px 20px;
padding: 0;
color: #000;
}
div.sphinxsidebar a {
color: #444;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type=text]{
margin-left: 20px;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #005B81;
text-decoration: none;
}
a:hover {
color: #E32E00;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: Arial, sans-serif;
background-color: #BED4EB;
font-weight: normal;
color: #212224;
margin: 30px 0px 10px 0px;
padding: 5px 0 5px 10px;
text-shadow: 0px 1px 0 white
}
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
line-height: 1.5em;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.highlight{
background-color: white;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 10px;
background-color: White;
color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.1em;
margin: 1.5em 0 1.5em 0;
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
font-size: 1.1em;
font-family: monospace;
}
.viewcode-back {
font-family: Arial, sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

BIN
docs/_static/noise_dk.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

339
docs/_static/sphinxdoc.css vendored Normal file
View file

@ -0,0 +1,339 @@
/*
* sphinxdoc.css_t
* ~~~~~~~~~~~~~~~
*
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
* Armin Ronacher for Werkzeug.
*
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
'Verdana', sans-serif;
font-size: 14px;
letter-spacing: -0.01em;
line-height: 150%;
text-align: center;
background-color: #BFD1D4;
color: black;
padding: 0;
border: 1px solid #aaa;
margin: 0px 80px 0px 80px;
min-width: 740px;
}
div.document {
background-color: white;
text-align: left;
background-image: url(contents.png);
background-repeat: repeat-x;
}
div.bodywrapper {
margin: 0 240px 0 0;
border-right: 1px solid #ccc;
}
div.body {
margin: 0;
padding: 0.5em 20px 20px 20px;
}
div.related {
font-size: 1em;
}
div.related ul {
background-image: url(navigation.png);
height: 2em;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
div.related ul li {
margin: 0;
padding: 0;
height: 2em;
float: left;
}
div.related ul li.right {
float: right;
margin-right: 5px;
}
div.related ul li a {
margin: 0;
padding: 0 5px 0 5px;
line-height: 1.75em;
color: #EE9816;
}
div.related ul li a:hover {
color: #3CA8E7;
}
div.sphinxsidebarwrapper {
padding: 0;
}
div.sphinxsidebar {
margin: 0;
padding: 0.5em 15px 15px 0;
width: 210px;
float: right;
font-size: 1em;
text-align: left;
}
div.sphinxsidebar h3, div.sphinxsidebar h4 {
margin: 1em 0 0.5em 0;
font-size: 1em;
padding: 0.1em 0 0.1em 0.5em;
color: white;
border: 1px solid #86989B;
background-color: #AFC1C4;
}
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar ul {
padding-left: 1.5em;
margin-top: 7px;
padding: 0;
line-height: 130%;
}
div.sphinxsidebar ul ul {
margin-left: 20px;
}
div.footer {
background-color: #E3EFF1;
color: #86989B;
padding: 3px 8px 3px 0;
clear: both;
font-size: 0.8em;
text-align: right;
}
div.footer a {
color: #86989B;
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
p {
margin: 0.8em 0 0.5em 0;
}
a {
color: #CA7900;
text-decoration: none;
}
a:hover {
color: #2491CF;
}
div.body a {
text-decoration: underline;
}
h1 {
margin: 0;
padding: 0.7em 0 0.3em 0;
font-size: 1.5em;
color: #11557C;
}
h2 {
margin: 1.3em 0 0.2em 0;
font-size: 1.35em;
padding: 0;
}
h3 {
margin: 1em 0 -0.3em 0;
font-size: 1.2em;
}
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
color: black!important;
}
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
display: none;
margin: 0 0 0 0.3em;
padding: 0 0.2em 0 0.2em;
color: #aaa!important;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
h5:hover a.anchor, h6:hover a.anchor {
display: inline;
}
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
h5 a.anchor:hover, h6 a.anchor:hover {
color: #777;
background-color: #eee;
}
a.headerlink {
color: #c60f0f!important;
font-size: 1em;
margin-left: 6px;
padding: 0 4px 0 4px;
text-decoration: none!important;
}
a.headerlink:hover {
background-color: #ccc;
color: white!important;
}
cite, code, tt {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
font-size: 0.95em;
letter-spacing: 0.01em;
}
tt {
background-color: #f2f2f2;
border-bottom: 1px solid #ddd;
color: #333;
}
tt.descname, tt.descclassname, tt.xref {
border: 0;
}
hr {
border: 1px solid #abc;
margin: 2em;
}
a tt {
border: 0;
color: #CA7900;
}
a tt:hover {
color: #2491CF;
}
pre {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
font-size: 0.95em;
letter-spacing: 0.015em;
line-height: 120%;
padding: 0.5em;
border: 1px solid #ccc;
background-color: #f8f8f8;
}
pre a {
color: inherit;
text-decoration: underline;
}
td.linenos pre {
padding: 0.5em 0;
}
div.quotebar {
background-color: #f8f8f8;
max-width: 250px;
float: right;
padding: 2px 7px;
border: 1px solid #ccc;
}
div.topic {
background-color: #f8f8f8;
}
table {
border-collapse: collapse;
margin: 0 -0.5em 0 -0.5em;
}
table td, table th {
padding: 0.2em 0.5em 0.2em 0.5em;
}
div.admonition, div.warning {
font-size: 0.9em;
margin: 1em 0 1em 0;
border: 1px solid #86989B;
background-color: #f7f7f7;
padding: 0;
}
div.admonition p, div.warning p {
margin: 0.5em 1em 0.5em 1em;
padding: 0;
}
div.admonition pre, div.warning pre {
margin: 0.4em 1em 0.4em 1em;
}
div.admonition p.admonition-title,
div.warning p.admonition-title {
margin: 0;
padding: 0.1em 0 0.1em 0.5em;
color: white;
border-bottom: 1px solid #86989B;
font-weight: bold;
background-color: #AFC1C4;
}
div.warning {
border: 1px solid #940000;
}
div.warning p.admonition-title {
background-color: #CF0000;
border-bottom-color: #940000;
}
div.admonition ul, div.admonition ol,
div.warning ul, div.warning ol {
margin: 0.1em 0.5em 0.5em 3em;
padding: 0;
}
div.versioninfo {
margin: 1em 0 0 0;
border: 1px solid #ccc;
background-color: #DDEAF0;
padding: 8px;
line-height: 1.3em;
font-size: 0.9em;
}
.viewcode-back {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
'Verdana', sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

8
docs/api/basexmpp.rst Normal file
View file

@ -0,0 +1,8 @@
========
BaseXMPP
========
.. module:: sleekxmpp.basexmpp
.. autoclass:: BaseXMPP
:members:

8
docs/api/clientxmpp.rst Normal file
View file

@ -0,0 +1,8 @@
==========
ClientXMPP
==========
.. module:: sleekxmpp.clientxmpp
.. autoclass:: ClientXMPP
:members:

View file

@ -0,0 +1,8 @@
=============
ComponentXMPP
=============
.. module:: sleekxmpp.componentxmpp
.. autoclass:: ComponentXMPP
:members:

14
docs/api/exceptions.rst Normal file
View file

@ -0,0 +1,14 @@
Exceptions
==========
.. module:: sleekxmpp.exceptions
.. autoexception:: XMPPError
:members:
.. autoexception:: IqError
:members:
.. autoexception:: IqTimeout
:members:

View file

@ -0,0 +1,12 @@
.. module:: sleekxmpp.xmlstream.filesocket
.. _filesocket:
Python 2.6 File Socket Shims
============================
.. autoclass:: FileSocket
:members:
.. autoclass:: Socket26
:members:

View file

@ -0,0 +1,24 @@
Stanza Handlers
===============
The Basic Handler
-----------------
.. module:: sleekxmpp.xmlstream.handler.base
.. autoclass:: BaseHandler
:members:
Callback
--------
.. module:: sleekxmpp.xmlstream.handler.callback
.. autoclass:: Callback
:members:
Waiter
------
.. module:: sleekxmpp.xmlstream.handler.waiter
.. autoclass:: Waiter
:members:

View file

@ -0,0 +1,7 @@
Jabber IDs (JID)
=================
.. module:: sleekxmpp.xmlstream.jid
.. autoclass:: JID
:members:

View file

@ -0,0 +1,41 @@
Stanza Matchers
===============
The Basic Matcher
-----------------
.. module:: sleekxmpp.xmlstream.matcher.base
.. autoclass:: MatcherBase
:members:
ID Matching
-----------
.. module:: sleekxmpp.xmlstream.matcher.id
.. autoclass:: MatcherId
:members:
Stanza Path Matching
--------------------
.. module:: sleekxmpp.xmlstream.matcher.stanzapath
.. autoclass:: StanzaPath
:members:
XPath
-----
.. module:: sleekxmpp.xmlstream.matcher.xpath
.. autoclass:: MatchXPath
:members:
XMLMask
-------
.. module:: sleekxmpp.xmlstream.matcher.xmlmask
.. autoclass:: MatchXMLMask
:members:

View file

@ -0,0 +1,11 @@
=========
Scheduler
=========
.. module:: sleekxmpp.xmlstream.scheduler
.. autoclass:: Task
:members:
.. autoclass:: Scheduler
:members:

View file

@ -0,0 +1,123 @@
.. _stanzabase:
==============
Stanza Objects
==============
.. module:: sleekxmpp.xmlstream.stanzabase
The :mod:`~sleekmxpp.xmlstream.stanzabase` module provides a wrapper for the
standard :mod:`~xml.etree.ElementTree` module that makes working with XML
less painful. Instead of having to manually move up and down an element
tree and insert subelements and attributes, you can interact with an object
that behaves like a normal dictionary or JSON object, which silently maps
keys to XML attributes and elements behind the scenes.
Overview
--------
The usefulness of this layer grows as the XML you have to work with
becomes nested. The base unit here, :class:`ElementBase`, can map to a
single XML element, or several depending on how advanced of a mapping
is desired from interface keys to XML structures. For example, a single
:class:`ElementBase` derived class could easily describe:
.. code-block:: xml
<message to="user@example.com" from="friend@example.com">
<body>Hi!</body>
<x:extra>
<x:item>Custom item 1</x:item>
<x:item>Custom item 2</x:item>
<x:item>Custom item 3</x:item>
</x:extra>
</message>
If that chunk of XML were put in the :class:`ElementBase` instance
``msg``, we could extract the data from the XML using::
>>> msg['extra']
['Custom item 1', 'Custom item 2', 'Custom item 3']
Provided we set up the handler for the ``'extra'`` interface to load the
``<x:item>`` element content into a list.
The key concept is that given an XML structure that will be repeatedly
used, we can define a set of :term:`interfaces` which when we read from,
write to, or delete, will automatically manipulate the underlying XML
as needed. In addition, some of these interfaces may in turn reference
child objects which expose interfaces for particularly complex child
elements of the original XML chunk.
.. seealso::
:ref:`create-stanza-interfaces`.
Because the :mod:`~sleekxmpp.xmlstream.stanzabase` module was developed
as part of an `XMPP <http://xmpp.org>`_ library, these chunks of XML are
referred to as :term:`stanzas <stanza>`, and in SleekXMPP we refer to a
subclass of :class:`ElementBase` which defines the interfaces needed for
interacting with a given :term:`stanza` a :term:`stanza object`.
To make dealing with more complicated and nested :term:`stanzas <stanza>`
or XML chunks easier, :term:`stanza objects <stanza object>` can be
composed in two ways: as iterable child objects or as plugins. Iterable
child stanzas, or :term:`substanzas`, are accessible through a special
``'substanzas'`` interface. This option is useful for stanzas which
may contain more than one of the same kind of element. When there is
only one child element, the plugin method is more useful. For plugins,
a parent stanza object delegates one of its XML child elements to the
plugin stanza object. Here is an example:
.. code-block:: xml
<iq type="result">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" type="bot" name="SleekXMPP Bot" />
</query>
</iq>
We can can arrange this stanza into two objects: an outer, wrapper object for
dealing with the ``<iq />`` element and its attributes, and a plugin object to
control the ``<query />`` payload element. If we give the plugin object the
name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then
we can access the plugin as so::
>>> iq['disco_info']
'<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" type="bot" name="SleekXMPP Bot" />
</query>'
We can then drill down through the plugin object's interfaces as desired::
>>> iq['disco_info']['identities']
[('client', 'bot', 'SleekXMPP Bot')]
Plugins may also add new interfaces to the parent stanza object as if they
had been defined by the parent directly, and can also override the behaviour
of an interface defined by the parent.
.. seealso::
- :ref:`create-stanza-plugins`
- :ref:`create-extension-plugins`
- :ref:`override-parent-interfaces`
Registering Stanza Plugins
--------------------------
.. autofunction:: register_stanza_plugin
ElementBase
-----------
.. autoclass:: ElementBase
:members:
:private-members:
:special-members:
StanzaBase
----------
.. autoclass:: StanzaBase
:members:

View file

@ -0,0 +1,46 @@
.. module:: sleekxmpp.xmlstream.tostring
.. _tostring:
XML Serialization
=================
Since the XML layer of SleekXMPP is based on :mod:`~xml.etree.ElementTree`,
why not just use the built-in :func:`~xml.etree.ElementTree.tostring`
method? The answer is that using that method produces ugly results when
using namespaces. The :func:`tostring()` method used here intelligently
hides namespaces when able and does not introduce excessive namespace
prefixes::
>>> from sleekxmpp.xmlstream.tostring import tostring
>>> from xml.etree import cElementTree as ET
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
>>> ET.tostring(xml)
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
>>> tostring(xml)
'<foo xmlns="bar"><baz /></foo>'
As a side effect of this namespace hiding, using :func:`tostring()` may
produce unexpected results depending on how the :func:`tostring()` method
is invoked. For example, when sending XML on the wire, the main XMPP
stanzas with their namespace of ``jabber:client`` will not include the
namespace because that is already declared by the stream header. But, if
you create a :class:`~sleekxmpp.stanza.message.Message` instance and dump
it to the terminal, the ``jabber:client`` namespace will appear.
.. autofunction:: tostring
Escaping Special Characters
---------------------------
In order to prevent errors when sending arbitrary text as the textual
content of an XML element, certain characters must be escaped. These
are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping
mechanism is to replace those characters with their equivalent escape
entities: ``&amp;``, ``&lt;``, ``&gt;``, ``&apos;``, and ``&quot;``.
In the future, the use of CDATA sections may be allowed to reduce the
size of escaped text or for when other XMPP processing agents do not
undertand these entities.
.. autofunction:: xml_escape

View file

@ -0,0 +1,10 @@
==========
XML Stream
==========
.. module:: sleekxmpp.xmlstream.xmlstream
.. autoexception:: RestartStream
.. autoclass:: XMLStream
:members:

177
docs/architecture.rst Normal file
View file

@ -0,0 +1,177 @@
.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP
SleekXMPP Architecture
======================
The core of SleekXMPP is contained in four classes: ``XMLStream``,
``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this
stack is a library for working with XML objects that eliminates most
of the tedium of creating/manipulating XML.
.. image:: _static/images/arch_layers.png
:height: 300px
:align: center
.. index:: XMLStream
The Foundation: XMLStream
-------------------------
:class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` is a mostly XMPP-agnostic
class whose purpose is to read and write from a bi-directional XML stream.
It also allows for callback functions to execute when XML matching given
patterns is received; these callbacks are also referred to as :term:`stream
handlers <stream handler>`. The class also provides a basic eventing system
which can be triggered either manually or on a timed schedule.
The Main Threads
~~~~~~~~~~~~~~~~
:class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` instances run using at
least three background threads: the send thread, the read thread, and the
scheduler thread. The send thread is in charge of monitoring the send queue
and writing text to the outgoing XML stream. The read thread pulls text off
of the incoming XML stream and stores the results in an event queue. The
scheduler thread is used to emit events after a given period of time.
Additionally, the main event processing loop may be executed in its
own thread if SleekXMPP is being used in the background for another
application.
Short-lived threads may also be spawned as requested for threaded
:term:`event handlers <event handler>`.
How XML Text is Turned into Action
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To demonstrate the flow of information, let's consider what happens
when this bit of XML is received (with an assumed namespace of
``jabber:client``):
.. code-block:: xml
<message to="user@example.com" from="friend@example.net">
<body>Hej!</body>
</message>
1. **Convert XML strings into objects.**
Incoming text is parsed and converted into XML objects (using
ElementTree) which are then wrapped into what are referred to as
:term:`Stanza objects <stanza object>`. The appropriate class for the
new object is determined using a map of namespaced element names to
classes.
Our incoming XML is thus turned into a :class:`~sleekxmpp.stanza.Message`
:term:`stanza object` because the namespaced element name
``{jabber:client}message`` is associated with the class
:class:`~sleekxmpp.stanza.Message`.
2. **Match stanza objects to callbacks.**
These objects are then compared against the stored patterns associated
with the registered callback handlers. For each match, a copy of the
:term:`stanza object` is paired with a reference to the handler and
placed into the event queue.
Our :class:`~sleekxmpp.stanza.Message` object is thus paired with the message stanza handler
:meth:`BaseXMPP._handle_message` to create the tuple::
('stanza', stanza_obj, handler)
3. **Process the event queue.**
The event queue is the heart of SleekXMPP. Nearly every action that
takes place is first inserted into this queue, whether that be received
stanzas, custom events, or scheduled events.
When the stanza is pulled out of the event queue with an associated
callback, the callback function is executed with the stanza as its only
parameter.
.. warning::
The callback, aka :term:`stream handler`, is executed in the main event
processing thread. If the handler blocks, event processing will also
block.
4. **Raise Custom Events**
Since a :term:`stream handler` shouldn't block, if extensive processing
for a stanza is required (such as needing to send and receive an
:class:`~sleekxmpp.stanza.Iq` stanza), then custom events must be used.
These events are not explicitly tied to the incoming XML stream and may
be raised at any time. Importantly, these events may be handled in their
own thread.
When the event is raised, a copy of the stanza is created for each
handler registered for the event. In contrast to :term:`stream handlers
<stream handler>`, these functions are referred to as :term:`event
handlers <event handler>`. Each stanza/handler pair is then put into the
event queue.
.. note::
It is possible to skip the event queue and process an event immediately
by using ``direct=True`` when raising the event.
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
raises a ``'message'`` event::
self.event('message', msg)
The event call then places the message object back into the event queue
paired with an :term:`event handler`::
('event', 'message', msg_copy1, custom_event_handler_1)
('event', 'message', msg_copy2, custom_evetn_handler_2)
5. **Process Custom Events**
The stanza and :term:`event handler` are then pulled from the event
queue, and the handler is executed, passing the stanza as its only
argument. If the handler was registered as threaded, then a new thread
will be spawned for it.
.. note::
Events may be raised without needing :term:`stanza objects <stanza object>`.
For example, you could use ``self.event('custom', {'a': 'b'})``.
You don't even need any arguments: ``self.event('no_parameters')``.
However, every event handler MUST accept at least one argument.
Finally, after a long trek, our message is handed off to the user's
custom handler in order to do awesome stuff::
msg.reply()
msg['body'] = "Hey! This is awesome!"
msg.send()
.. index:: BaseXMPP, XMLStream
Raising XMPP Awareness: BaseXMPP
--------------------------------
While :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` attempts to shy away
from anything too XMPP specific, :class:`~sleekxmpp.basexmpp.BaseXMPP`'s
sole purpose is to provide foundational support for sending and receiving
XMPP stanzas. This support includes registering the basic message,
presence, and iq stanzas, methods for creating and sending stanzas, and
default handlers for incoming messages and keeping track of presence
notifications.
The plugin system for adding new XEP support is also maintained by
:class:`~sleekxmpp.basexmpp.BaseXMPP`.
.. index:: ClientXMPP, BaseXMPP
ClientXMPP
----------
:class:`~sleekxmpp.clientxmpp.ClientXMPP` extends
:class:`~sleekxmpp.clientxmpp.BaseXMPP` with additional logic for connecting
to an XMPP server by performing DNS lookups. It also adds support for stream
features such as STARTTLS and SASL.
.. index:: ComponentXMPP, BaseXMPP
ComponentXMPP
-------------
:class:`~sleekxmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of
:class:`~sleekxmpp.basexmpp.BaseXMPP` that implements the component handshake
protocol.

222
docs/conf.py Normal file
View file

@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
#
# SleekXMPP documentation build configuration file, created by
# sphinx-quickstart on Tue Aug 9 22:27:06 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'SleekXMPP'
copyright = u'2011, Nathan Fritz, Lance Stout'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0RC3'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'tango'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {'headingcolor': '#CFCFCF', 'linkcolor': '#4A7389'}
# 00ADEE
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = 'SleekXMPP'
# A shorter title for the navigation bar. Default is the same as html_title.
html_short_title = '%s Documentation' % release
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
html_additional_pages = {
}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'SleekXMPPdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'SleekXMPP.tex', u'SleekXMPP Documentation',
u'Nathan Fritz, Lance Stout', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'sleekxmpp', u'SleekXMPP Documentation',
[u'Nathan Fritz, Lance Stout'], 1)
]
intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')}

679
docs/create_plugin.rst Normal file
View file

@ -0,0 +1,679 @@
.. _create-plugin:
Creating a SleekXMPP Plugin
===========================
One of the goals of SleekXMPP is to provide support for every draft or final
XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, SleekXMPP has a
plugin mechanism for adding the functionalities required by each XEP. But even
though plugins were made to quickly implement and prototype the official XMPP
extensions, there is no reason you can't create your own plugin to implement
your own custom XMPP-based protocol.
This guide will help walk you through the steps to
implement a rudimentary version of `XEP-0077 In-band
Registration <http://xmpp.org/extensions/xep-0077.html>`_. In-band registration
was implemented in example 14-6 (page 223) of `XMPP: The Definitive
Guide <http://oreilly.com/catalog/9780596521271>`_ because there was no SleekXMPP
plugin for XEP-0077 at the time of writing. We will partially fix that issue
here by turning the example implementation from *XMPP: The Definitive Guide*
into a plugin. Again, note that this will not a complete implementation, and a
different, more robust, official plugin for XEP-0077 may be added to SleekXMPP
in the future.
.. note::
The example plugin created in this guide is for the server side of the
registration process only. It will **NOT** be able to register new accounts
on an XMPP server.
First Steps
-----------
Every plugin inherits from the class :mod:`base_plugin <sleekxmpp.plugins.base.base_plugin>`,
and must include a ``plugin_init`` method. While the
plugins distributed with SleekXMPP must be placed in the plugins directory
``sleekxmpp/plugins`` to be loaded, custom plugins may be loaded from any
module. To do so, use the following form when registering the plugin:
.. code-block:: python
self.register_plugin('myplugin', module=mod_containing_my_plugin)
The plugin name must be the same as the plugin's class name.
Now, we can open our favorite text editors and create ``xep_0077.py`` in
``SleekXMPP/sleekxmpp/plugins``. We want to do some basic house-keeping and
declare the name and description of the XEP we are implementing. If you
are creating your own custom plugin, you don't need to include the ``xep``
attribute.
.. code-block:: python
"""
Creating a SleekXMPP Plugin
This is a minimal implementation of XEP-0077 to serve
as a tutorial for creating SleekXMPP plugins.
"""
from sleekxmpp.plugins.base import base_plugin
class xep_0077(base_plugin):
"""
XEP-0077 In-Band Registration
"""
def plugin_init(self):
self.description = "In-Band Registration"
self.xep = "0077"
Now that we have a basic plugin, we need to edit
``sleekxmpp/plugins/__init__.py`` to include our new plugin by adding
``'xep_0077'`` to the ``__all__`` declaration.
Interacting with Other Plugins
------------------------------
In-band registration is a feature that should be advertised through `Service
Discovery <http://xmpp.org/extensions/xep-0030.html>`_. To do that, we tell the
``xep_0030`` plugin to add the ``"jabber:iq:register"`` feature. We put this
call in a method named ``post_init`` which will be called once the plugin has
been loaded; by doing so we advertise that we can do registrations only after we
finish activating the plugin.
The ``post_init`` method needs to call ``base_plugin.post_init(self)``
which will mark that ``post_init`` has been called for the plugin. Once the
SleekXMPP object begins processing, ``post_init`` will be called on any plugins
that have not already run ``post_init``. This allows you to register plugins and
their dependencies without needing to worry about the order in which you do so.
**Note:** by adding this call we have introduced a dependency on the XEP-0030
plugin. Be sure to register ``'xep_0030'`` as well as ``'xep_0077'``. SleekXMPP
does not automatically load plugin dependencies for you.
.. code-block:: python
def post_init(self):
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature("jabber:iq:register")
Creating Custom Stanza Objects
------------------------------
Now, the IQ stanzas needed to implement our version of XEP-0077 are not very
complex, and we could just interact with the XML objects directly just like
in the *XMPP: The Definitive Guide* example. However, creating custom stanza
objects is good practice.
We will create a new ``Registration`` stanza. Following the *XMPP: The
Definitive Guide* example, we will add support for a username and password
field. We also need two flags: ``registered`` and ``remove``. The ``registered``
flag is sent when an already registered user attempts to register, along with
their registration data. The ``remove`` flag is a request to unregister a user's
account.
Adding additional `fields specified in
XEP-0077 <http://xmpp.org/extensions/xep-0077.html#registrar-formtypes-register>`_
will not be difficult and is left as an exercise for the reader.
Our ``Registration`` class needs to start with a few descriptions of its
behaviour:
* ``namespace``
The namespace our stanza object lives in. In this case,
``"jabber:iq:register"``.
* ``name``
The name of the root XML element. In this case, the ``query`` element.
* ``plugin_attrib``
The name to access this type of stanza. In particular, given a
registration stanza, the ``Registration`` object can be found using:
``iq_object['register']``.
* ``interfaces``
A list of dictionary-like keys that can be used with the stanza object.
When using ``"key"``, if there exists a method of the form ``getKey``,
``setKey``, or``delKey`` (depending on context) then the result of calling
that method will be returned. Otherwise, the value of the attribute ``key``
of the main stanza element is returned if one exists.
**Note:** The accessor methods currently use title case, and not camel case.
Thus if you need to access an item named ``"methodName"`` you will need to
use ``getMethodname``. This naming convention might change to full camel
case in a future version of SleekXMPP.
* ``sub_interfaces``
A subset of ``interfaces``, but these keys map to the text of any
subelements that are direct children of the main stanza element. Thus,
referencing ``iq_object['register']['username']`` will either execute
``getUsername`` or return the value in the ``username`` element of the
query.
If you need to access an element, say ``elem``, that is not a direct child
of the main stanza element, you will need to add ``getElem``, ``setElem``,
and ``delElem``. See the note above about naming conventions.
.. code-block:: python
from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
from sleekxmpp import Iq
class Registration(ElementBase):
namespace = 'jabber:iq:register'
name = 'query'
plugin_attrib = 'register'
interfaces = set(('username', 'password', 'registered', 'remove'))
sub_interfaces = interfaces
def getRegistered(self):
present = self.xml.find('{%s}registered' % self.namespace)
return present is not None
def getRemove(self):
present = self.xml.find('{%s}remove' % self.namespace)
return present is not None
def setRegistered(self, registered):
if registered:
self.addField('registered')
else:
del self['registered']
def setRemove(self, remove):
if remove:
self.addField('remove')
else:
del self['remove']
def addField(self, name):
itemXML = ET.Element('{%s}%s' % (self.namespace, name))
self.xml.append(itemXML)
Setting a ``sub_interface`` attribute to ``""`` will remove that subelement.
Since we want to include empty registration fields in our form, we need the
``addField`` method to add the empty elements.
Since the ``registered`` and ``remove`` elements are just flags, we need to add
custom logic to enforce the binary behavior.
Extracting Stanzas from the XML Stream
--------------------------------------
Now that we have a custom stanza object, we need to be able to detect when we
receive one. To do this, we register a stream handler that will pattern match
stanzas off of the XML stream against our stanza object's element name and
namespace. To do so, we need to create a ``Callback`` object which contains
an XML fragment that can identify our stanza type. We can add this handler
registration to our ``plugin_init`` method.
Also, we need to associate our ``Registration`` class with IQ stanzas;
that requires the use of the ``register_stanza_plugin`` function (in
``sleekxmpp.xmlstream.stanzabase``) which takes the class of a parent stanza
type followed by the substanza type. In our case, the parent stanza is an IQ
stanza, and the substanza is our registration query.
The ``__handleRegistration`` method referenced in the callback will be our
handler function to process registration requests.
.. code-block:: python
def plugin_init(self):
self.description = "In-Band Registration"
self.xep = "0077"
self.xmpp.registerHandler(
Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration))
register_stanza_plugin(Iq, Registration)
Handling Incoming Stanzas and Triggering Events
-----------------------------------------------
There are six situations that we need to handle to finish our implementation of
XEP-0077.
**Registration Form Request from a New User:**
.. code-block:: xml
<iq type="result">
<query xmlns="jabber:iq:register">
<username />
<password />
</query>
</iq>
**Registration Form Request from an Existing User:**
.. code-block:: xml
<iq type="result">
<query xmlns="jabber:iq:register">
<registered />
<username>Foo</username>
<password>hunter2</password>
</query>
</iq>
**Unregister Account:**
.. code-block:: xml
<iq type="result">
<query xmlns="jabber:iq:register" />
</iq>
**Incomplete Registration:**
.. code-block:: xml
<iq type="error">
<query xmlns="jabber:iq:register">
<username>Foo</username>
</query>
<error code="406" type="modify">
<not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
</iq>
**Conflicting Registrations:**
.. code-block:: xml
<iq type="error">
<query xmlns="jabber:iq:register">
<username>Foo</username>
<password>hunter2</password>
</query>
<error code="409" type="cancel">
<conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
</iq>
**Successful Registration:**
.. code-block:: xml
<iq type="result">
<query xmlns="jabber:iq:register" />
</iq>
Cases 1 and 2: Registration Requests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Responding to registration requests depends on if the requesting user already
has an account. If there is an account, the response should include the
``registered`` flag and the user's current registration information. Otherwise,
we just send the fields for our registration form.
We will handle both cases by creating a ``sendRegistrationForm`` method that
will create either an empty of full form depending on if we provide it with
user data. Since we need to know which form fields to include (especially if we
add support for the other fields specified in XEP-0077), we will also create a
method ``setForm`` which will take the names of the fields we wish to include.
.. code-block:: python
def plugin_init(self):
self.description = "In-Band Registration"
self.xep = "0077"
self.form_fields = ('username', 'password')
... remainder of plugin_init
...
def __handleRegistration(self, iq):
if iq['type'] == 'get':
# Registration form requested
userData = self.backend[iq['from'].bare]
self.sendRegistrationForm(iq, userData)
def setForm(self, *fields):
self.form_fields = fields
def sendRegistrationForm(self, iq, userData=None):
reg = iq['register']
if userData is None:
userData = {}
else:
reg['registered'] = True
for field in self.form_fields:
data = userData.get(field, '')
if data:
# Add field with existing data
reg[field] = data
else:
# Add a blank field
reg.addField(field)
iq.reply().setPayload(reg.xml)
iq.send()
Note how we are able to access our ``Registration`` stanza object with
``iq['register']``.
A User Backend
++++++++++++++
You might have noticed the reference to ``self.backend``, which is an object
that abstracts away storing and retrieving user information. Since it is not
much more than a dictionary, we will leave the implementation details to the
final, full source code example.
Case 3: Unregister an Account
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The next simplest case to consider is responding to a request to remove
an account. If we receive a ``remove`` flag, we instruct the backend to
remove the user's account. Since your application may need to know about
when users are registered or unregistered, we trigger an event using
``self.xmpp.event('unregister_user', iq)``. See the component examples below for
how to respond to that event.
.. code-block:: python
def __handleRegistration(self, iq):
if iq['type'] == 'get':
# Registration form requested
userData = self.backend[iq['from'].bare]
self.sendRegistrationForm(iq, userData)
elif iq['type'] == 'set':
# Remove an account
if iq['register']['remove']:
self.backend.unregister(iq['from'].bare)
self.xmpp.event('unregistered_user', iq)
iq.reply().send()
return
Case 4: Incomplete Registration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For the next case we need to check the user's registration to ensure it has all
of the fields we wanted. The simple option that we will use is to loop over the
field names and check each one; however, this means that all fields we send to
the user are required. Adding optional fields is left to the reader.
Since we have received an incomplete form, we need to send an error message back
to the user. We have to send a few different types of errors, so we will also
create a ``_sendError`` method that will add the appropriate ``error`` element
to the IQ reply.
.. code-block:: python
def __handleRegistration(self, iq):
if iq['type'] == 'get':
# Registration form requested
userData = self.backend[iq['from'].bare]
self.sendRegistrationForm(iq, userData)
elif iq['type'] == 'set':
if iq['register']['remove']:
# Remove an account
self.backend.unregister(iq['from'].bare)
self.xmpp.event('unregistered_user', iq)
iq.reply().send()
return
for field in self.form_fields:
if not iq['register'][field]:
# Incomplete Registration
self._sendError(iq, '406', 'modify', 'not-acceptable'
"Please fill in all fields.")
return
...
def _sendError(self, iq, code, error_type, name, text=''):
iq.reply().setPayload(iq['register'].xml)
iq.error()
iq['error']['code'] = code
iq['error']['type'] = error_type
iq['error']['condition'] = name
iq['error']['text'] = text
iq.send()
Cases 5 and 6: Conflicting and Successful Registration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We are down to the final decision on if we have a successful registration. We
send the user's data to the backend with the ``self.backend.register`` method.
If it returns ``True``, then registration has been successful. Otherwise,
there has been a conflict with usernames and registration has failed. Like
with unregistering an account, we trigger an event indicating that a user has
been registered by using ``self.xmpp.event('registered_user', iq)``. See the
component examples below for how to respond to this event.
.. code-block:: python
def __handleRegistration(self, iq):
if iq['type'] == 'get':
# Registration form requested
userData = self.backend[iq['from'].bare]
self.sendRegistrationForm(iq, userData)
elif iq['type'] == 'set':
if iq['register']['remove']:
# Remove an account
self.backend.unregister(iq['from'].bare)
self.xmpp.event('unregistered_user', iq)
iq.reply().send()
return
for field in self.form_fields:
if not iq['register'][field]:
# Incomplete Registration
self._sendError(iq, '406', 'modify', 'not-acceptable',
"Please fill in all fields.")
return
if self.backend.register(iq['from'].bare, iq['register']):
# Successful registration
self.xmpp.event('registered_user', iq)
iq.reply().setPayload(iq['register'].xml)
iq.send()
else:
# Conflicting registration
self._sendError(iq, '409', 'cancel', 'conflict',
"That username is already taken.")
Example Component Using the XEP-0077 Plugin
-------------------------------------------
Alright, the moment we've been working towards - actually using our plugin to
simplify our other applications. Here is a basic component that simply manages
user registrations and sends the user a welcoming message when they register,
and a farewell message when they delete their account.
Note that we have to register the ``'xep_0030'`` plugin first,
and that we specified the form fields we wish to use with
``self.xmpp.plugin['xep_0077'].setForm('username', 'password')``.
.. code-block:: python
import sleekxmpp.componentxmpp
class Example(sleekxmpp.componentxmpp.ComponentXMPP):
def __init__(self, jid, password):
sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888)
self.registerPlugin('xep_0030')
self.registerPlugin('xep_0077')
self.plugin['xep_0077'].setForm('username', 'password')
self.add_event_handler("registered_user", self.reg)
self.add_event_handler("unregistered_user", self.unreg)
def reg(self, iq):
msg = "Welcome! %s" % iq['register']['username']
self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
def unreg(self, iq):
msg = "Bye! %s" % iq['register']['username']
self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
**Congratulations!** We now have a basic, functioning implementation of
XEP-0077.
Complete Source Code for XEP-0077 Plugin
----------------------------------------
Here is a copy of a more complete implementation of the plugin we created, but
with some additional registration fields implemented.
.. code-block:: python
"""
Creating a SleekXMPP Plugin
This is a minimal implementation of XEP-0077 to serve
as a tutorial for creating SleekXMPP plugins.
"""
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
from sleekxmpp import Iq
import copy
class Registration(ElementBase):
namespace = 'jabber:iq:register'
name = 'query'
plugin_attrib = 'register'
interfaces = set(('username', 'password', 'email', 'nick', 'name',
'first', 'last', 'address', 'city', 'state', 'zip',
'phone', 'url', 'date', 'misc', 'text', 'key',
'registered', 'remove', 'instructions'))
sub_interfaces = interfaces
def getRegistered(self):
present = self.xml.find('{%s}registered' % self.namespace)
return present is not None
def getRemove(self):
present = self.xml.find('{%s}remove' % self.namespace)
return present is not None
def setRegistered(self, registered):
if registered:
self.addField('registered')
else:
del self['registered']
def setRemove(self, remove):
if remove:
self.addField('remove')
else:
del self['remove']
def addField(self, name):
itemXML = ET.Element('{%s}%s' % (self.namespace, name))
self.xml.append(itemXML)
class UserStore(object):
def __init__(self):
self.users = {}
def __getitem__(self, jid):
return self.users.get(jid, None)
def register(self, jid, registration):
username = registration['username']
def filter_usernames(user):
return user != jid and self.users[user]['username'] == username
conflicts = filter(filter_usernames, self.users.keys())
if conflicts:
return False
self.users[jid] = registration
return True
def unregister(self, jid):
del self.users[jid]
class xep_0077(base_plugin):
"""
XEP-0077 In-Band Registration
"""
def plugin_init(self):
self.description = "In-Band Registration"
self.xep = "0077"
self.form_fields = ('username', 'password')
self.form_instructions = ""
self.backend = UserStore()
self.xmpp.registerHandler(
Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration))
register_stanza_plugin(Iq, Registration)
def post_init(self):
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature("jabber:iq:register")
def __handleRegistration(self, iq):
if iq['type'] == 'get':
# Registration form requested
userData = self.backend[iq['from'].bare]
self.sendRegistrationForm(iq, userData)
elif iq['type'] == 'set':
if iq['register']['remove']:
# Remove an account
self.backend.unregister(iq['from'].bare)
self.xmpp.event('unregistered_user', iq)
iq.reply().send()
return
for field in self.form_fields:
if not iq['register'][field]:
# Incomplete Registration
self._sendError(iq, '406', 'modify', 'not-acceptable',
"Please fill in all fields.")
return
if self.backend.register(iq['from'].bare, iq['register']):
# Successful registration
self.xmpp.event('registered_user', iq)
iq.reply().setPayload(iq['register'].xml)
iq.send()
else:
# Conflicting registration
self._sendError(iq, '409', 'cancel', 'conflict',
"That username is already taken.")
def setForm(self, *fields):
self.form_fields = fields
def setInstructions(self, instructions):
self.form_instructions = instructions
def sendRegistrationForm(self, iq, userData=None):
reg = iq['register']
if userData is None:
userData = {}
else:
reg['registered'] = True
if self.form_instructions:
reg['instructions'] = self.form_instructions
for field in self.form_fields:
data = userData.get(field, '')
if data:
# Add field with existing data
reg[field] = data
else:
# Add a blank field
reg.addField(field)
iq.reply().setPayload(reg.xml)
iq.send()
def _sendError(self, iq, code, error_type, name, text=''):
iq.reply().setPayload(iq['register'].xml)
iq.error()
iq['error']['code'] = code
iq['error']['type'] = error_type
iq['error']['condition'] = name
iq['error']['text'] = text
iq.send()

271
docs/event_index.rst Normal file
View file

@ -0,0 +1,271 @@
Event Index
===========
.. glossary::
:sorted:
connected
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
Signal that a connection has been made with the XMPP server, but a session
has not yet been established.
changed_status
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
Triggered when a presence stanza is received from a JID with a show type
different than the last presence stanza from the same JID.
changed_subscription
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
Triggered whenever a presence stanza with a type of ``subscribe``,
``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received.
Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe``
are set to ``True`` or ``False``, and not ``None``, then SleekXMPP will
either accept or reject all subscription requests before your event handlers
are called. Set these values to ``None`` if you wish to make more complex
subscription decisions.
chatstate_active
- **Data:**
- **Source:**
chatstate_composing
- **Data:**
- **Source:**
chatstate_gone
- **Data:**
- **Source:**
chatstate_inactive
- **Data:**
- **Source:**
chatstate_paused
- **Data:**
- **Source:**
disco_info
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoInfo`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
Triggered whenever a ``disco#info`` result stanza is received.
disco_items
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoItems`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
Triggered whenever a ``disco#items`` result stanza is received.
disconnected
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
Signal that the connection with the XMPP server has been lost.
entity_time
- **Data:**
- **Source:**
failed_auth
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`, :py:class:`~sleekxmpp.plugins.xep_0078.xep_0078`
Signal that the server has rejected the provided login credentials.
gmail_notify
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
Signal that there are unread emails for the Gmail account associated with the current XMPP account.
gmail_messages
- **Data:** :py:class:`~sleekxmpp.Iq`
- **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
Signal that there are unread emails for the Gmail account associated with the current XMPP account.
got_online
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
If a presence stanza is received from a JID which was previously marked as
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
or '``xa``', then this event is triggered as well.
got_offline
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
Signal that an unavailable presence stanza has been received from a JID.
groupchat_invite
- **Data:**
- **Source:**
groupchat_direct_invite
- **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
groupchat_message
- **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
Triggered whenever a message is received from a multi-user chat room.
groupchat_presence
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
Triggered whenever a presence stanza is received from a user in a multi-user chat room.
groupchat_subject
- **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room.
killed
- **Data:**
- **Source:**
last_activity
- **Data:**
- **Source:**
message
- **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
Makes the contents of message stanzas available whenever one is received. Be
sure to check the message type in order to handle error messages.
message_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Currently the same as :term:`message_xform`.
message_xform
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Triggered whenever a data form is received inside a message.
mucc::[room]::got_offline
- **Data:**
- **Source:**
muc::[room]::got_online
- **Data:**
- **Source:**
muc::[room]::message
- **Data:**
- **Source:**
muc::[room]::presence
- **Data:**
- **Source:**
presence_available
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``available``' is received.
presence_error
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``error``' is received.
presence_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
This event is present in the XEP-0004 plugin code, but is currently not used.
presence_probe
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``probe``' is received.
presence_subscribe
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``subscribe``' is received.
presence_subscribed
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``subscribed``' is received.
presence_unavailable
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``unavailable``' is received.
presence_unsubscribe
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``unsubscribe``' is received.
presence_unsubscribed
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
A presence stanza with a type of '``unsubscribed``' is received.
roster_update
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
An IQ result containing roster entries is received.
sent_presence
- **Data:** ``{}``
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
Signal that an initial presence stanza has been written to the XML stream.
session_end
- **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but
future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
will distinguish the two events.
Plugins that maintain session-based state should clear themselves when
this event is fired.
session_start
- **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
Signal that a connection to the XMPP server has been made and a session has been established.
socket_error
- **Data:** ``Socket`` exception object
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
stream_error
- **Data:** :py:class:`~sleekxmpp.stanza.StreamError`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`

2
docs/features.rst Normal file
View file

@ -0,0 +1,2 @@
How to Use Stream Features
==========================

View file

@ -0,0 +1,75 @@
.. _echocomponent:
=================================
Create and Run a Server Component
=================================
.. note::
If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
If you have not yet installed SleekXMPP, do so now by either checking out a version
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
or ``easy_install``.
.. code-block:: sh
pip install sleekxmpp # Or: easy_install sleekxmpp
Many XMPP applications eventually graduate to requiring to run as a server
component in order to meet scalability requirements. To demonstrate how to
turn an XMPP client bot into a component, we'll turn the echobot example
(:ref:`echobot`) into a component version.
The first difference is that we will add an additional import statement:
.. code-block:: python
from sleekxmpp.componentxmpp import ComponentXMPP
Likewise, we will change the bot's class definition to match:
.. code-block:: python
class EchoComponent(ComponentXMPP):
def __init__(self, jid, secret, server, port):
ComponentXMPP.__init__(self, jid, secret, server, port)
A component instance requires two extra parameters compared to a client
instance: ``server`` and ``port``. These specifiy the name and port of
the XMPP server that will be accepting the component. For example, for
a MUC component, the following could be used:
.. code-block:: python
muc = ComponentXMPP('muc.sleekxmpp.com', '******', 'sleekxmpp.com', 5555)
.. note::
The ``server`` value is **NOT** derived from the provided JID for the
component, unlike with client connections.
One difference with the component version is that we do not have
to handle the :term:`session_start` event if we don't wish to deal
with presence.
The other, main difference with components is that the
``'from'`` value for every stanza must be explicitly set, since
components may send stanzas from multiple JIDs. To do so,
the :meth:`~sleekxmpp.basexmpp.BaseXMPP.send_message()` and
:meth:`~sleekxmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters
``mfrom`` and ``pfrom``, respectively. For any method that uses
:class:`~sleekxmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used.
Final Product
-------------
.. include:: ../../examples/echo_component.py
:literal:

View file

@ -0,0 +1,390 @@
.. _echobot:
===============================
SleekXMPP Quickstart - Echo Bot
===============================
.. note::
If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
If you have not yet installed SleekXMPP, do so now by either checking out a version
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
or ``easy_install``.
.. code-block:: sh
pip install sleekxmpp # Or: easy_install sleekxmpp
As a basic starting project, we will create an echo bot which will reply to any
messages sent to it. We will also go through adding some basic command line configuration
for enabling or disabling debug log outputs and setting the username and password
for the bot.
For the command line options processing, we will use the built-in ``optparse``
module and the ``getpass`` module for reading in passwords.
TL;DR Just Give Me the Code
---------------------------
As you wish: :ref:`the completed example <echobot_complete>`.
Overview
--------
To get started, here is a brief outline of the structure that the final project will have:
.. code-block:: python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import logging
import getpass
from optparse import OptionParser
import sleekxmpp
'''Here we will create out echo bot class'''
if __name__ == '__main__':
'''Here we will configure and read command line options'''
'''Here we will instantiate our echo bot'''
'''Finally, we connect the bot and start listening for messages'''
Default Encoding
----------------
XMPP requires support for UTF-8 and so SleekXMPP must use UTF-8 as well. In
Python3 this is simple because Unicode is the default string type. For Python2.6+
the situation is not as easy because standard strings are simply byte arrays and
use ASCII. We can get Python to use UTF-8 as the default encoding by including:
.. code-block:: python
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
.. warning::
Until we are able to ensure that SleekXMPP will always use Unicode in Python2.6+, this
may cause issues embedding SleekXMPP into other applications which assume ASCII encoding.
Creating the EchoBot Class
--------------------------
There are three main types of entities within XMPP — servers, components, and
clients. Since our echo bot will only be responding to a few people, and won't need
to remember thousands of users, we will use a client connection. A client connection
is the same type that you use with your standard IM client such as Pidgin or Psi.
SleekXMPP comes with a :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` class
which we can extend to add our message echoing feature. :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>`
requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those
as well.
.. code-block:: python
class EchoBot(sleekxmpp.ClientXMPP):
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
Handling Session Start
~~~~~~~~~~~~~~~~~~~~~~
The XMPP spec requires clients to broadcast its presence and retrieve its roster (buddy list) once
it connects and establishes a session with the XMPP server. Until these two tasks are completed,
some servers may not deliver or send messages or presence notifications to the client. So we now
need to be sure that we retrieve our roster and send an initial presence once the session has
started. To do that, we will register an event handler for the :term:`session_start` event.
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
self.add_event_handler('session_start', self.start)
Since we want the method ``self.start`` to execute when the :term:`session_start` event is triggered,
we also need to define the ``self.start`` handler.
.. code-block:: python
def start(self, event):
self.send_presence()
self.get_roster()
.. warning::
Not sending an initial presence and retrieving the roster when using a client instance can
prevent your program from receiving presence notifications or messages depending on the
XMPP server you have chosen.
Our event handler, like every event handler, accepts a single parameter which typically is the stanza
that was received that caused the event. In this case, ``event`` will just be an empty dictionary since
there is no associated data.
Our first task of sending an initial presence is done using :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>`.
Calling :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>` without any arguments will send the simplest
stanza allowed in XMPP:
.. code-block:: xml
<presence />
The second requirement is fulfilled using :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>`, which
will send an IQ stanza requesting the roster to the server and then wait for the response. You may be wondering
what :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>` returns since we are not saving any return
value. The roster data is saved by an internal handler to ``self.roster``, and in the case of a :class:`ClientXMPP
<sleekxmpp.clientxmpp.ClientXMPP>` instance to ``self.client_roster``. (The difference between ``self.roster`` and
``self.client_roster`` is that ``self.roster`` supports storing roster information for multiple JIDs, which is useful
for components, whereas ``self.client_roster`` stores roster data for just the client's JID.)
It is possible for a timeout to occur while waiting for the server to respond, which can happen if the
network is excessively slow or the server is no longer responding. In that case, an :class:`IQTimeout
<sleekxmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <sleekxmpp.exceptions.IQError>` exception can
be raised if the request contained bad data or requested the roster for the wrong user. In either case, you can wrap the
``get_roster()`` call in a ``try``/``except`` block to retry the roster retrieval process.
The XMPP stanzas from the roster retrieval process could look like this:
.. code-block:: xml
<iq type="get">
<query xmlns="jabber:iq:roster" />
</iq>
<iq type="result" to="echobot@example.com" from="example.com">
<query xmlns="jabber:iq:roster">
<item jid="friend@example.com" subscription="both" />
</query>
</iq>
Responding to Messages
~~~~~~~~~~~~~~~~~~~~~~
Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and
responding to messages. Now we can register a handler for the :term:`message` event that is raised
whenever a messsage is received.
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
self.add_event_handler('session_start', self.start)
self.add_event_handler('message', self.message)
The :term:`message` event is fired whenever a ``<message />`` stanza is received, including for
group chat messages, errors, etc. Properly responding to messages thus requires checking the
``'type'`` interface of the message :term:`stanza object`. For responding to only messages
addressed to our bot (and not from a chat room), we check that the type is either ``normal``
or ``chat``. (Other potential types are ``error``, ``headline``, and ``groupchat``.)
.. code-block:: python
def message(self, msg):
if msg['type'] in ('normal', 'chat'):
msg.reply("Thanks for sending:\n%s" % msg['body']).send()
Let's take a closer look at the ``.reply()`` method used above. For message stanzas,
``.reply()`` accepts the parameter ``body`` (also as the first positional argument),
which is then used as the value of the ``<body />`` element of the message.
Setting the appropriate ``to`` JID is also handled by ``.reply()``.
Another way to have sent the reply message would be to use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`,
which is a convenience method for generating and sending a message based on the values passed to it. If we were to use
this method, the above code would look as so:
.. code-block:: python
def message(self, msg):
if msg['type'] in ('normal', 'chat'):
self.send_message(mto=msg['from'],
mbody='Thanks for sending:\n%s' % msg['body'])
Whichever method you choose to use, the results in action will look like this:
.. code-block:: xml
<message to="echobot@example.com" from="someuser@example.net" type="chat">
<body>Hej!</body>
</message>
<message to="someuser@example.net" type="chat">
<body>Thanks for sending:
Hej!</body>
</message>
.. note::
XMPP does not require stanzas sent by a client to include a ``from`` attribute, and
leaves that responsibility to the XMPP server. However, if a sent stanza does
include a ``from`` attribute, it must match the full JID of the client or some
servers will reject it. SleekXMPP thus leaves out the ``from`` attribute when replying
using a client connection.
Command Line Arguments and Logging
----------------------------------
While this isn't part of SleekXMPP itself, we do want our echo bot program to be able
to accept a JID and password from the command line instead of hard coding them. We will
use the ``optparse`` module for this, though there are several alternative methods, including
the newer ``argparse`` module.
We want to accept three parameters: the JID for the echo bot, its password, and a flag for
displaying the debugging logs. We also want these to be optional parameters, since passing
a password directly through the command line can be a security risk.
.. code-block:: python
if __name__ == '__main__':
optp = OptionParser()
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
opts, args = optp.parse_args()
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
Since we included a flag for enabling debugging logs, we need to configure the
``logging`` module to behave accordingly.
.. code-block:: python
if __name__ == '__main__':
# .. option parsing from above ..
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
Connecting to the Server and Processing
---------------------------------------
There are three steps remaining until our echo bot is complete:
1. We need to instantiate the bot.
2. The bot needs to connect to an XMPP server.
3. We have to instruct the bot to start running and processing messages.
Creating the bot is straightforward, but we can also perform some configuration
at this stage. For example, let's say we want our bot to support `service discovery
<http://xmpp.org/extensions/xep-0030.html>`_ and `pings <http://xmpp.org/extensions/xep-0199.html>`_:
.. code-block:: python
if __name__ == '__main__':
# .. option parsing and logging steps from above
xmpp = EchoBot(opts.jid, opts.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0199') # Ping
If the ``EchoBot`` class had a hard dependency on a plugin, we could register that plugin in
the ``EchoBot.__init__`` method instead.
.. note::
If you are using the OpenFire server, you will need to include an additional
configuration step. OpenFire supports a different version of SSL than what
most servers and SleekXMPP support.
.. code-block:: python
import ssl
xmpp.ssl_version = ssl.PROTOCOL_SSLv3
Now we're ready to connect and begin echoing messages. If you have the package
``dnspython`` installed, then the :meth:`sleekxmpp.clientxmpp.ClientXMPP` method
will perform a DNS query to find the appropriate server to connect to for the
given JID. If you do not have ``dnspython``, then SleekXMPP will attempt to
connect to the hostname used by the JID, unless an address tuple is supplied
to :meth:`sleekxmpp.clientxmpp.ClientXMPP`.
.. code-block:: python
if __name__ == '__main__':
# .. option parsing & echo bot configuration
if xmpp.connect():
xmpp.process(block=True)
else:
print('Unable to connect')
.. note::
For Google Talk users withouth ``dnspython`` installed, the above code
should look like:
.. code-block:: python
if __name__ == '__main__':
# .. option parsing & echo bot configuration
if xmpp.connect(('talk.google.com', 5222)):
xmpp.process(block=True)
else:
print('Unable to connect')
To begin responding to messages, you'll see we called :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
which will start the event handling, send queue, and XML reader threads. It will also call
the :meth:`sleekxmpp.plugins.base.base_plugin.post_init` method on all registered plugins. By
passing ``block=True`` to :meth:`sleekxmpp.basexmpp.BaseXMPP.process` we are running the
main processing loop in the main thread of execution. The :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
call will not return until after SleekXMPP disconnects. If you need to run the client in the background
for another program, use ``block=False`` to spawn the processing loop in its own thread.
.. note::
Before 1.0, controlling the blocking behaviour of :meth:`sleekxmpp.basexmpp.BaseXMPP.process` was
done via the ``threaded`` argument. This arrangement was a source of confusion because some users
interpreted that as controlling whether or not SleekXMPP used threads at all, instead of how
the processing loop itself was spawned.
The statements ``xmpp.process(threaded=False)`` and ``xmpp.process(block=True)`` are equivalent.
.. _echobot_complete:
The Final Product
-----------------
Here then is what the final result should look like after working through the guide above. The code
can also be found in the SleekXMPP `examples directory <http://github.com/fritzy/SleekXMPP/tree/master/examples>`_.
.. compound::
You can run the code using:
.. code-block:: sh
python echobot.py -d -j echobot@example.com
which will prompt for the password and then begin echoing messages. To test, open
your regular IM client and start a chat with the echo bot. Messages you send to it should
be mirrored back to you. Be careful if you are using the same JID for the echo bot that
you also have logged in with another IM client. Messages could be routed to your IM client instead
of the bot.
.. include:: ../../examples/echo_client.py
:literal:

182
docs/getting_started/iq.rst Normal file
View file

@ -0,0 +1,182 @@
Send/Receive IQ Stanzas
=======================
Unlike :class:`~sleekxmpp.stanza.message.Message` and
:class:`~sleekxmpp.stanza.presence.Presence` stanzas which only use
text data for basic usage, :class:`~sleekxmpp.stanza.iq.Iq` stanzas
require using XML payloads, and generally entail creating a new
SleekXMPP plugin to provide the necessary convenience methods to
make working with them easier.
Basic Use
---------
XMPP's use of :class:`~sleekxmpp.stanza.iq.Iq` stanzas is built around
namespaced ``<query />`` elements. For clients, just sending the
empty ``<query />`` element will suffice for retrieving information. For
example, a very basic implementation of service discovery would just
need to be able to send:
.. code-block:: xml
<iq to="user@example.com" type="get" id="1">
<query xmlns="http://jabber.org/protocol/disco#info" />
</iq>
Creating Iq Stanzas
~~~~~~~~~~~~~~~~~~~
SleekXMPP provides built-in support for creating basic :class:`~sleekxmpp.stanza.iq.Iq`
stanzas this way. The relevant methods are:
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq`
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_get`
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_set`
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_result`
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_error`
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_query`
These methods all follow the same pattern: create or modify an existing
:class:`~sleekxmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based
on the method name, and finally add a ``<query />`` element with the given
namespace. For example, to produce the query above, you would use:
.. code-block:: python
self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info',
ito='user@example.com')
Sending Iq Stanzas
~~~~~~~~~~~~~~~~~~
Once an :class:`~sleekxmpp.stanza.iq.Iq` stanza is created, sending it
over the wire is done using its :meth:`~sleekxmpp.stanza.iq.Iq.send()`
method, like any other stanza object. However, there are a few extra
options to control how to wait for the query's response.
These options are:
* ``block``: The default behaviour is that :meth:`~sleekxmpp.stanza.iq.Iq.send()`
will block until a response is received and the response stanza will be the
return value. Setting ``block`` to ``False`` will cause the call to return
immediately. In which case, you will need to arrange some way to capture
the response stanza if you need it.
* ``timeout``: When using the blocking behaviour, the call will eventually
timeout with an error. The default timeout is 30 seconds, but this may
be overidden two ways. To change the timeout globally, set:
.. code-block:: python
self.response_timeout = 10
To change the timeout for a single call, the ``timeout`` parameter works:
.. code-block:: python
iq.send(timeout=60)
* ``callback``: When not using a blocking call, using the ``callback``
argument is a simple way to register a handler that will execute
whenever a response is finally received. Using this method, there
is no timeout limit. In case you need to remove the callback, the
name of the newly created callback is returned.
.. code-block:: python
cb_name = iq.send(callback=self.a_callback)
# ... later if we need to cancel
self.remove_handler(cb_name)
Properly working with :class:`~sleekxmpp.stanza.iq.Iq` stanzas requires
handling the intended, normal flow, error responses, and timed out
requests. To make this easier, two exceptions may be thrown by
:meth:`~sleekxmpp.stanza.iq.Iq.send()`: :exc:`~sleekxmpp.exceptions.IqError`
and :exc:`~sleekxmpp.exceptions.IqTimeout`. These exceptions only
apply to the default, blocking calls.
.. code-block:: python
try:
resp = iq.send()
# ... do stuff with expected Iq result
except IqError as e:
err_resp = e.iq
# ... handle error case
except IqTimeout:
# ... no response received in time
pass
If you do not care to distinguish between errors and timeouts, then you
can combine both cases with a generic :exc:`~sleekxmpp.exceptions.XMPPError`
exception:
.. code-block:: python
try:
resp = iq.send()
except XMPPError:
# ... Don't care about the response
pass
Advanced Use
------------
Going beyond the basics provided by SleekXMPP requires building at least a
rudimentary SleekXMPP plugin to create a :term:`stanza object` for
interfacting with the :class:`~sleekxmpp.stanza.iq.Iq` payload.
.. seealso::
* :ref:`create-plugin`
* :ref:`work-with-stanzas`
* :ref:`using-handlers-matchers`
The typical way to respond to :class:`~sleekxmpp.stanza.iq.Iq` requests is
to register stream handlers. As an example, suppose we create a stanza class
named ``CustomXEP`` which uses the XML element ``<query xmlns="custom-xep" />``,
and has a :attr:`~sleekxmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value
of ``custom_xep``.
There are two types of incoming :class:`~sleekxmpp.stanza.iq.Iq` requests:
``get`` and ``set``. You can register a handler that will accept both and then
filter by type as needed, as so:
.. code-block:: python
self.register_handler(Callback(
'CustomXEP Handler',
StanzaPath('iq/custom_xep'),
self._handle_custom_iq))
# ...
def _handle_custom_iq(self, iq):
if iq['type'] == 'get':
# ...
pass
elif iq['type'] == 'set':
# ...
pass
else:
# ... This will capture error responses too
pass
If you want to filter out query types beforehand, you can adjust the matching
filter by using ``@type=get`` or ``@type=set`` if you are using the recommended
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher.
.. code-block:: python
self.register_handler(Callback(
'CustomXEP Handler',
StanzaPath('iq@type=get/custom_xep'),
self._handle_custom_iq_get))
# ...
def _handle_custom_iq_get(self, iq):
assert(iq['type'] == 'get')

View file

@ -0,0 +1,2 @@
Mulit-User Chat (MUC) Bot
=========================

View file

@ -0,0 +1,2 @@
Manage Presence Subscriptions
=============================

View file

@ -0,0 +1,42 @@
.. _proxy:
=========================
Enable HTTP Proxy Support
=========================
.. note::
If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
In some instances, you may wish to route XMPP traffic through
an HTTP proxy, probably to get around restrictive firewalls.
SleekXMPP provides support for basic HTTP proxying with DIGEST
authentication.
Enabling proxy support is done in two steps. The first is to instruct SleekXMPP
to use a proxy, and the second is to configure the proxy details:
.. code-block:: python
xmpp = ClientXMPP(...)
xmpp.use_proxy = True
xmpp.proxy_config = {
'host': 'proxy.example.com',
'port': 5555,
'username': 'example_user',
'password': '******'
}
The ``'username'`` and ``'password'`` fields are optional if the proxy does not
require authentication.
The Final Product
-----------------
.. include:: ../../examples/proxy_echo_client.py
:literal:

View file

@ -0,0 +1,2 @@
Send a Message Every 5 Minutes
==============================

View file

@ -0,0 +1,94 @@
Sign in, Send a Message, and Disconnect
=======================================
.. note::
If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
A common use case for SleekXMPP is to send one-off messages from
time to time. For example, one use case could be sending out a notice when
a shell script finishes a task.
We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To
start, we create a client class based on :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and
register a handler for the :term:`session_start` event. We will also accept parameters
for the JID that will receive our message, and the string content of the message.
.. code-block:: python
import sleekxmpp
class SendMsgBot(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, recipient, msg):
super(SendMsgBot, self).__init__(jid, password)
self.recipient = recipient
self.msg = msg
self.add_event_handler('session_start', self.start)
def start(self, event):
self.send_presence()
self.get_roster()
Note that as in :ref:`echobot`, we need to include send an initial presence and request
the roster. Next, we want to send our message, and to do that we will use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`.
.. code-block:: python
def start(self, event):
self.send_presence()
self.get_roster()
self.send_message(mto=self.recipient, mbody=self.msg)
Finally, we need to disconnect the client using :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>`.
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
:meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
for the client to disconnect before the send queue is processed and the message is actually
sent on the wire. To ensure that our message is processed, we use
:meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`.
.. code-block:: python
def start(self, event):
self.send_presence()
self.get_roster()
self.send_message(mto=self.recipient, mbody=self.msg)
self.disconnect(wait=True)
.. warning::
If you happen to be adding stanzas to the send queue faster than the send thread
can process them, then :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`
will block and not disconnect.
Final Product
-------------
.. compound::
The final step is to create a small runner script for initialising our ``SendMsgBot`` class and adding some
basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive
at the code below. To experiment with this example, you can use:
.. code-block:: sh
python send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message"
which will prompt for the password and then log in, send your message, and then disconnect. To test, open
your regular IM client with the account you wish to send messages to. When you run the ``send_client.py``
example and instruct it to send your IM client account a message, you should receive the message you
gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists)
then you will also see the ``SendMsgBot`` client come online and then go offline.
.. include:: ../../examples/send_client.py
:literal:

35
docs/glossary.rst Normal file
View file

@ -0,0 +1,35 @@
.. _glossary:
Glossary
========
.. glossary::
:sorted:
stream handler
A callback function that accepts stanza objects pulled directly
from the XML stream. A stream handler is encapsulated in a
object that includes a :term:`Matcher` object, and which provides
additional semantics. For example, the ``Waiter`` handler wrapper
blocks thread execution until a matching stanza is received.
event handler
A callback function that responds to events raised by
``XMLStream.event``. An event handler may be marked as
threaded, allowing it to execute outside of the main processing
loop.
stanza object
Informally may refer both to classes which extend ``ElementBase``
or ``StanzaBase``, and to objects of such classes.
A stanza object is a wrapper for an XML object which exposes ``dict``
like interfaces which may be assigned to, read from, or deleted.
stanza plugin
A :term:`stanza object` which has been registered as a potential child
of another stanza object. The plugin stanza may accessed through the
parent stanza using the plugin's ``plugin_attrib`` as an interface.
substanza
See :term:`stanza plugin`

201
docs/guide_xep_0030.rst Normal file
View file

@ -0,0 +1,201 @@
XEP-0030: Working with Service Discovery
========================================
XMPP networks can be composed of many individual clients, components,
and servers. Determining the JIDs for these entities and the various
features they may support is the role of `XEP-0030, Service
Discovery <http://xmpp.org/extensions/xep-0030.html>`_, or "disco" for short.
Every XMPP entity may possess what are called nodes. A node is just a name for
some aspect of an XMPP entity. For example, if an XMPP entity provides `Ad-Hoc
Commands <http://xmpp.org/extensions/xep-0050.html>`_, then it will have a node
named ``http://jabber.org/protocol/commands`` which will contain information
about the commands provided. Other agents using these ad-hoc commands will
interact with the information provided by this node. Note that the node name is
just an identifier; there is no inherent meaning.
Working with service discovery is about creating and querying these nodes.
According to XEP-0030, a node may contain three types of information:
identities, features, and items. (Further, extensible, information types are
defined in `XEP-0128 <http://xmpp.org/extensions/xep-0128.html>`_, but they are
not yet implemented by SleekXMPP.) SleekXMPP provides methods to configure each
of these node attributes.
Configuring Service Discovery
-----------------------------
The design focus for the XEP-0030 plug-in is handling info and items requests
in a dynamic fashion, allowing for complex policy decisions of who may receive
information and how much, or use alternate backend storage mechanisms for all
of the disco data. To do this, each action that the XEP-0030 plug-in performs
is handed off to what is called a "node handler," which is just a callback
function. These handlers are arranged in a hierarchy that allows for a single
handler to manage an entire domain of JIDs (say for a component), while allowing
other handler functions to override that global behaviour for certain JIDs, or
even further limited to only certain JID and node combinations.
The Dynamic Handler Hierarchy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ``global``: (JID is None, node is None)
Handlers assigned at this level for an action (such as ``add_feature``) provide a global default
behaviour when the action is performed.
* ``jid``: (JID assigned, node is None)
At this level, handlers provide a default behaviour for actions affecting any node owned by the
JID in question. This level is most useful for component connections; there is effectively no
difference between this and the global level when using a client connection.
* ``node``: (JID assigned, node assigned)
A handler for this level is responsible for carrying out an action for only one node, and is the
most specific handler type available. These types of handlers will be most useful for "special"
nodes that require special processing different than others provided by the JID, such as using
access control lists, or consolidating data from other nodes.
Default Static Handlers
~~~~~~~~~~~~~~~~~~~~~~~
The XEP-0030 plug-in provides a default set of handlers that work using in-memory
disco stanzas. Each handler simply performs the appropriate lookup or storage
operation using these stanzas without doing any complex operations such as
checking an ACL, etc.
You may find it necessary at some point to revert a particular node or JID to
using the default, static handlers. To do so, use the method ``make_static()``.
You may also elect to only convert a given set of actions instead.
Creating a Node Handler
~~~~~~~~~~~~~~~~~~~~~~~
Every node handler receives three arguments: the JID, the node, and a data
parameter that will contain the relevant information for carrying out the
handler's action, typically a dictionary.
The JID will always have a value, defaulting to ``xmpp.boundjid.full`` for
components or ``xmpp.boundjid.bare`` for clients. The node value may be None or
a string.
Only handlers for the actions ``get_info`` and ``get_items`` need to have return
values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as
output. It is also acceptable for handlers for these actions to generate an
XMPPError exception when necessary.
Example Node Handler:
+++++++++++++++++++++
Here is one of the built-in default handlers as an example:
.. code-block:: python
def add_identity(self, jid, node, data):
"""
Add a new identity to the JID/node combination.
The data parameter may provide:
category -- The general category to which the agent belongs.
itype -- A more specific designation with the category.
name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value.
"""
self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
Adding Identities, Features, and Items
--------------------------------------
In order to maintain some backwards compatibility, the methods ``add_identity``,
``add_feature``, and ``add_item`` do not follow the method signature pattern of
the other API methods (i.e. jid, node, then other options), but rather retain
the parameter orders from previous plug-in versions.
Adding an Identity
~~~~~~~~~~~~~~~~~~
Adding an identity may be done using either the older positional notation, or
with keyword parameters. The example below uses the keyword arguments, but in
the same order as expected using positional arguments.
.. code-block:: python
xmpp['xep_0030'].add_identity(category='client',
itype='bot',
name='Sleek',
node='foo',
jid=xmpp.boundjid.full,
lang='no')
The JID and node values determine which handler will be used to perform the
``add_identity`` action.
The ``lang`` parameter allows for adding localized versions of identities using
the ``xml:lang`` attribute.
Adding a Feature
~~~~~~~~~~~~~~~~
The position ordering for ``add_feature()`` is to include the feature, then
specify the node and then the JID. The JID and node values determine which
handler will be used to perform the ``add_feature`` action.
.. code-block:: python
xmpp['xep_0030'].add_feature(feature='jabber:x:data',
node='foo',
jid=xmpp.boundjid.full)
Adding an Item
~~~~~~~~~~~~~~
The parameters to ``add_item()`` are potentially confusing due to the fact that
adding an item requires two JID and node combinations: the JID and node of the
item itself, and the JID and node that will own the item.
.. code-block:: python
xmpp['xep_0030'].add_item(jid='myitemjid@example.com',
name='An Item!',
node='owner_node',
subnode='item_node',
ijid=xmpp.boundjid.full)
.. note::
In this case, the owning JID and node are provided with the
parameters ``ijid`` and ``node``.
Peforming Disco Queries
-----------------------
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
and their nodes for disco information. Since these methods are wrappers for
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
method. The ``get_items()`` method may also accept the boolean parameter
``iterator``, which when set to ``True`` will return an iterator object using
the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
.. code-block:: python
info = self['xep_0030'].get_info(jid='foo@example.com',
node='bar',
ifrom='baz@mycomponent.example.com',
block=True,
timeout=30)
items = self['xep_0030'].get_info(jid='foo@example.com',
node='bar',
iterator=True)
For more examples on how to use basic disco queries, check the ``disco_browser.py``
example in the ``examples`` directory.
Local Queries
~~~~~~~~~~~~~
In some cases, it may be necessary to query the contents of a node owned by the
client itself, or one of a component's many JIDs. The same method is used as for
normal queries, with two differences. First, the parameter ``local=True`` must
be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not
a full Iq stanza.
.. code-block:: python
info = self['xep_0030'].get_info(node='foo', local=True)
items = self['xep_0030'].get_items(jid='somejid@mycomponent.example.com',
node='bar',
local=True)

View file

@ -0,0 +1,4 @@
.. _using-handlers-matchers:
Using Stream Handlers and Matchers
==================================

30
docs/howto/stanzas.rst Normal file
View file

@ -0,0 +1,30 @@
.. _work-with-stanzas:
How to Work with Stanza Objects
===============================
.. _create-stanza-interfaces:
Defining Stanza Interfaces
--------------------------
.. _create-stanza-plugins:
Creating Stanza Plugins
-----------------------
.. _create-extension-plugins:
Creating a Stanza Extension
---------------------------
.. _override-parent-interfaces:
Overriding a Parent Stanza
--------------------------

179
docs/index.rst Normal file
View file

@ -0,0 +1,179 @@
SleekXMPP
#########
.. sidebar:: Get the Code
.. code-block:: sh
pip install sleekxmpp
The latest source code for SleekXMPP may be found on `Github
<http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
``master`` branch, while the latest development version is in the
``develop`` branch.
**Latest Stable Release**
- `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_
**Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
A mailing list and XMPP chat room are available for discussing and getting
help with SleekXMPP.
**Mailing List**
`SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
**Chat**
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
SleekXMPP is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+,
and is featured in examples in
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
here from reading the Definitive Guide, please see the notes on updating
the examples to the latest version of SleekXMPP.
SleekXMPP's design goals and philosphy are:
**Low number of dependencies**
Installing and using SleekXMPP should be as simple as possible, without
having to deal with long dependency chains.
As part of reducing the number of dependencies, some third party
modules are included with SleekXMPP in the ``thirdparty`` directory.
Imports from this module first try to import an existing installed
version before loading the packaged version, when possible.
**Every XEP as a plugin**
Following Python's "batteries included" approach, the goal is to
provide support for all currently active XEPs (final and draft). Since
adding XEP support is done through easy to create plugins, the hope is
to also provide a solid base for implementing and creating experimental
XEPs.
**Rewarding to work with**
As much as possible, SleekXMPP should allow things to "just work" using
sensible defaults and appropriate abstractions. XML can be ugly to work
with, but it doesn't have to be that way.
Getting Started (with Examples)
-------------------------------
.. toctree::
:maxdepth: 1
getting_started/echobot
getting_started/sendlogout
getting_started/component
getting_started/presence
getting_started/muc
getting_started/proxy
getting_started/scheduler
getting_started/iq
Tutorials, FAQs, and How To Guides
----------------------------------
.. toctree::
:maxdepth: 1
faq
xeps
xmpp_tdg
howto/stanzas
create_plugin
features
sasl
handlersmatchers
Plugin Guides
~~~~~~~~~~~~~
.. toctree::
:maxdepth: 1
guide_xep_0030
SleekXMPP Architecture and Design
---------------------------------
.. toctree::
:maxdepth: 3
architecture
plugin_arch
API Reference
-------------
.. toctree::
:maxdepth: 2
event_index
api/clientxmpp
api/componentxmpp
api/basexmpp
api/exceptions
api/xmlstream/jid
api/xmlstream/stanzabase
api/xmlstream/handler
api/xmlstream/matcher
api/xmlstream/xmlstream
api/xmlstream/scheduler
api/xmlstream/tostring
api/xmlstream/filesocket
Core Stanzas
~~~~~~~~~~~~
.. toctree::
:maxdepth: 2
api/stanza/rootstanza
api/stanza/message
api/stanza/presence
api/stanza/iq
api/stanza/error
api/stanza/stream_error
Plugins
~~~~~~~
.. toctree::
:maxdepth: 2
Additional Info
---------------
.. toctree::
:hidden:
glossary
license
* :ref:`license`
* :ref:`glossary`
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
Credits
-------
**Main Author:** Nathan Fritz
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
`@fritzy <http://twitter.com/fritzy>`_
Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
<http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP
Council.
**Co-Author:** Lance Stout
`lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
`@lancestout <http://twitter.com/lancestout>`_
**Contributors:**
- Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
- Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
- Florent Le Coz (`louiz <http://github.com/louiz>`_)
- Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
- Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
- Te-jé Rogers (`te-je <http://github.com/te-je>`_)
- Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)

5
docs/license.rst Normal file
View file

@ -0,0 +1,5 @@
.. _license:
License (MIT)
=============
.. include:: ../LICENSE

170
docs/make.bat Normal file
View file

@ -0,0 +1,170 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SleekXMPP.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SleekXMPP.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

2
docs/plugin_arch.rst Normal file
View file

@ -0,0 +1,2 @@
Plugin Architecture
===================

BIN
docs/python-objects.inv Normal file

Binary file not shown.

2
docs/sasl.rst Normal file
View file

@ -0,0 +1,2 @@
How SASL Authentication Works
=============================

50
docs/xeps.rst Normal file
View file

@ -0,0 +1,50 @@
Supported XEPS
==============
======= ============================= ================
XEP Description Notes
======= ============================= ================
`0004`_ Data forms
`0009`_ Jabber RPC
`0012`_ Last Activity
`0030`_ Service Discovery
`0033`_ Extended Stanza Addressing
`0045`_ Multi-User Chat (MUC) Client-side only
`0050`_ Ad-hoc Commands
`0059`_ Result Set Management
`0060`_ Publish/Subscribe (PubSub) Client-side only
`0066`_ Out-of-band Data
`0078`_ Non-SASL Authentication
`0082`_ XMPP Date and Time Profiles
`0085`_ Chat-State Notifications
`0086`_ Error Condition Mappings
`0092`_ Software Version
`0128`_ Service Discovery Extensions
`0202`_ Entity Time
`0203`_ Delayed Delivery
`0224`_ Attention
`0249`_ Direct MUC Invitations
======= ============================= ================
.. _0004: http://xmpp.org/extensions/xep-0004.html
.. _0009: http://xmpp.org/extensions/xep-0009.html
.. _0012: http://xmpp.org/extensions/xep-0012.html
.. _0030: http://xmpp.org/extensions/xep-0030.html
.. _0033: http://xmpp.org/extensions/xep-0033.html
.. _0045: http://xmpp.org/extensions/xep-0045.html
.. _0050: http://xmpp.org/extensions/xep-0050.html
.. _0059: http://xmpp.org/extensions/xep-0059.html
.. _0060: http://xmpp.org/extensions/xep-0060.html
.. _0066: http://xmpp.org/extensions/xep-0066.html
.. _0078: http://xmpp.org/extensions/xep-0078.html
.. _0082: http://xmpp.org/extensions/xep-0082.html
.. _0085: http://xmpp.org/extensions/xep-0085.html
.. _0086: http://xmpp.org/extensions/xep-0086.html
.. _0092: http://xmpp.org/extensions/xep-0092.html
.. _0128: http://xmpp.org/extensions/xep-0128.html
.. _0199: http://xmpp.org/extensions/xep-0199.html
.. _0202: http://xmpp.org/extensions/xep-0202.html
.. _0203: http://xmpp.org/extensions/xep-0203.html
.. _0224: http://xmpp.org/extensions/xep-0224.html
.. _0249: http://xmpp.org/extensions/xep-0249.html

249
docs/xmpp_tdg.rst Normal file
View file

@ -0,0 +1,249 @@
Following *XMPP: The Definitive Guide*
======================================
SleekXMPP was featured in the first edition of the O'Reilly book
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271/>`_
by Peter Saint-Andre, Kevin Smith, and Remko Tronçon. The original source code
for the book's examples can be found at http://github.com/remko/xmpp-tdg. An
updated version of the source code, maintained to stay current with the latest
SleekXMPP release, is available at http://github.com/legastero/xmpp-tdg.
However, since publication, SleekXMPP has advanced from version 0.2.1 to version
1.0 and there have been several major API changes. The most notable is the
introduction of :term:`stanza objects <stanza object>` which have simplified and
standardized interactions with the XMPP XML stream.
What follows is a walk-through of *The Definitive Guide* highlighting the
changes needed to make the code examples work with version 1.0 of SleekXMPP.
These changes have been kept to a minimum to preserve the correlation with
the book's explanations, so be aware that some code may not use current best
practices.
Example 2-2. (Page 26)
----------------------
**Implementation of a basic bot that echoes all incoming messages back to its sender.**
The echo bot example requires a change to the ``handleIncomingMessage`` method
to reflect the use of the ``Message`` :term:`stanza object`. The
``"jid"`` field of the message object should now be ``"from"`` to match the
``from`` attribute of the actual XML message stanza. Likewise, ``"message"``
changes to ``"body"`` to match the ``body`` element of the message stanza.
Updated Code
~~~~~~~~~~~~
.. code-block:: python
def handleIncomingMessage(self, message):
self.xmpp.sendMessage(message["from"], message["body"])
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_
Example 14-1. (Page 215)
------------------------
**CheshiR IM bot implementation.**
The main event handling method in the Bot class is meant to process both message
events and presence update events. With the new changes in SleekXMPP 1.0,
extracting a CheshiR status "message" from both types of stanzas
requires accessing different attributes. In the case of a message stanza, the
``"body"`` attribute would contain the CheshiR message. For a presence event,
the information is stored in the ``"status"`` attribute. To handle both cases,
we can test the type of the given event object and look up the proper attribute
based on the type.
Like in the EchoBot example, the expression ``event["jid"]`` needs to change
to ``event["from"]`` in order to get a JID object for the stanza's sender.
Because other functions in CheshiR assume that the JID is a string, the ``jid``
attribute is used to access the string version of the JID. A check is also added
in case ``user`` is ``None``, but the check could (and probably should) be
placed in ``addMessageFromUser``.
Another change is needed in ``handleMessageAddedToBackend`` where
an HTML-IM response is created. The HTML content should be enclosed in a single
element, such as a ``<p>`` tag.
Updated Code
~~~~~~~~~~~~
.. code-block:: python
def handleIncomingXMPPEvent(self, event):
msgLocations = {sleekxmpp.stanza.presence.Presence: "status",
sleekxmpp.stanza.message.Message: "body"}
message = event[msgLocations[type(event)]]
user = self.backend.getUserFromJID(event["from"].jid)
if user is not None:
self.backend.addMessageFromUser(message, user)
def handleMessageAddedToBackend(self, message) :
body = message.user + ": " + message.text
htmlBody = "<p><a href='%(uri)s'>%(user)s</a>: %(message)s</p>" % {
"uri": self.url + "/" + message.user,
"user" : message.user, "message" : message.text }
for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
self.xmpp.sendMessage(subscriberJID, body, mhtml=htmlBody)
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_
Example 14-3. (Page 217)
------------------------
**Configurable CheshiR IM bot implementation.**
.. note::
Since the CheshiR examples build on each other, see previous sections for
corrections to code that is not marked as new in the book example.
The main difference for the configurable IM bot is the handling for the
data form in ``handleConfigurationCommand``. The test for equality
with the string ``"1"`` is no longer required; SleekXMPP converts
boolean data form fields to the values ``True`` and ``False``
automatically.
For the method ``handleIncomingXMPPPresence``, the attribute
``"jid"`` is again converted to ``"from"`` to get a JID
object for the presence stanza's sender, and the ``jid`` attribute is
used to access the string version of that JID object. A check is also added in
case ``user`` is ``None``, but the check could (and probably
should) be placed in ``getShouldMonitorPresenceFromUser``.
Updated Code
~~~~~~~~~~~~
.. code-block:: python
def handleConfigurationCommand(self, form, sessionId):
values = form.getValues()
monitorPresence =values["monitorPresence"]
jid = self.xmpp.plugin["xep_0050"].sessions[sessionId]["jid"]
user = self.backend.getUserFromJID(jid)
self.backend.setShouldMonitorPresenceFromUser(user, monitorPresence)
def handleIncomingXMPPPresence(self, event):
user = self.backend.getUserFromJID(event["from"].jid)
if user is not None:
if self.backend.getShouldMonitorPresenceFromUser(user):
self.handleIncomingXMPPEvent(event)
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_
Example 14-4. (Page 220)
------------------------
**CheshiR IM server component implementation.**
.. note::
Since the CheshiR examples build on each other, see previous sections for
corrections to code that is not marked as new in the book example.
Like several previous examples, a needed change is to replace
``subscription["from"]`` with ``subscription["from"].jid`` because the
``BaseXMPP`` method ``makePresence`` requires the JID to be a string.
A correction needs to be made in ``handleXMPPPresenceProbe`` because a line was
left out of the original implementation; the variable ``user`` is undefined. The
JID of the user can be extracted from the presence stanza's ``from`` attribute.
Since this implementation of CheshiR uses an XMPP component, it must
include a ``from`` attribute in all messages that it sends. Adding the
``from`` attribute is done by including ``mfrom=self.xmpp.jid`` in calls to
``self.xmpp.sendMessage``.
Updated Code
~~~~~~~~~~~~
.. code-block:: python
def handleXMPPPresenceProbe(self, event) :
self.xmpp.sendPresence(pto = event["from"])
def handleXMPPPresenceSubscription(self, subscription) :
if subscription["type"] == "subscribe" :
userJID = subscription["from"].jid
self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribed")
self.xmpp.sendPresence(pto = userJID)
self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribe")
def handleMessageAddedToBackend(self, message) :
body = message.user + ": " + message.text
for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
self.xmpp.sendMessage(subscriberJID, body, mfrom=self.xmpp.jid)
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_
Example 14-6. (Page 223)
------------------------
**CheshiR IM server component with in-band registration support.**
.. note::
Since the CheshiR examples build on each other, see previous sections for
corrections to code that is not marked as new in the book example.
After applying the changes from Example 14-4 above, the registrable component
implementation should work correctly.
.. tip::
To see how to implement in-band registration as a SleekXMPP plugin,
see the tutorial :ref:`tutorial-create-plugin`.
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_
Example 14-7. (Page 225)
------------------------
**Extended CheshiR IM server component implementation.**
.. note::
Since the CheshiR examples build on each other, see previous
sections for corrections to code that is not marked as new in the book
example.
While the final code example can look daunting with all of the changes
made, it requires very few modifications to work with the latest version of
SleekXMPP. Most differences are the result of CheshiR's backend functions
expecting JIDs to be strings so that they can be stripped to bare JIDs. To
resolve these, use the ``jid`` attribute of the JID objects. Also,
references to ``"message"`` and ``"jid"`` attributes need to
be changed to either ``"body"`` or ``"status"``, and either
``"from"`` or ``"to"`` depending on if the object is a message
or presence stanza and which of the JIDs from the stanza is needed.
Updated Code
~~~~~~~~~~~~
.. code-block:: python
def handleIncomingXMPPMessage(self, event) :
message = self.addRecipientToMessage(event["body"], event["to"].jid)
user = self.backend.getUserFromJID(event["from"].jid)
self.backend.addMessageFromUser(message, user)
def handleIncomingXMPPPresence(self, event) :
if event["to"].jid == self.componentDomain :
user = self.backend.getUserFromJID(event["from"].jid)
self.backend.addMessageFromUser(event["status"], user)
...
def handleXMPPPresenceSubscription(self, subscription) :
if subscription["type"] == "subscribe" :
userJID = subscription["from"].jid
user = self.backend.getUserFromJID(userJID)
contactJID = subscription["to"]
self.xmpp.sendPresenceSubscription(
pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user)
self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID)
if contactJID == self.componentDomain :
self.sendAllContactSubscriptionRequestsToUser(userJID)
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ |
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_