diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..a520f6a --- /dev/null +++ b/docs/Makefile @@ -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 ' where 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." diff --git a/docs/_static/agogo.css b/docs/_static/agogo.css new file mode 100644 index 0000000..8cdbf9c --- /dev/null +++ b/docs/_static/agogo.css @@ -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; +} \ No newline at end of file diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 0000000..888716a --- /dev/null +++ b/docs/_static/basic.css @@ -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; + } +} + diff --git a/docs/_static/default.css b/docs/_static/default.css new file mode 100644 index 0000000..21f3f50 --- /dev/null +++ b/docs/_static/default.css @@ -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; +} \ No newline at end of file diff --git a/docs/_static/fonts/Museo_Slab_500.otf b/docs/_static/fonts/Museo_Slab_500.otf new file mode 100644 index 0000000..84ceaca Binary files /dev/null and b/docs/_static/fonts/Museo_Slab_500.otf differ diff --git a/docs/_static/fonts/Museo_Slab_500italic.otf b/docs/_static/fonts/Museo_Slab_500italic.otf new file mode 100644 index 0000000..a8c055f Binary files /dev/null and b/docs/_static/fonts/Museo_Slab_500italic.otf differ diff --git a/docs/_static/fonts/OFLGoudyStMTT-Italic.ttf b/docs/_static/fonts/OFLGoudyStMTT-Italic.ttf new file mode 100644 index 0000000..91956cb Binary files /dev/null and b/docs/_static/fonts/OFLGoudyStMTT-Italic.ttf differ diff --git a/docs/_static/fonts/OFLGoudyStMTT.ttf b/docs/_static/fonts/OFLGoudyStMTT.ttf new file mode 100644 index 0000000..dc900c2 Binary files /dev/null and b/docs/_static/fonts/OFLGoudyStMTT.ttf differ diff --git a/docs/_static/fonts/YanoneKaffeesatz-Bold.ttf b/docs/_static/fonts/YanoneKaffeesatz-Bold.ttf new file mode 100644 index 0000000..64e5d1c Binary files /dev/null and b/docs/_static/fonts/YanoneKaffeesatz-Bold.ttf differ diff --git a/docs/_static/fonts/YanoneKaffeesatz-Light.ttf b/docs/_static/fonts/YanoneKaffeesatz-Light.ttf new file mode 100644 index 0000000..15eccf5 Binary files /dev/null and b/docs/_static/fonts/YanoneKaffeesatz-Light.ttf differ diff --git a/docs/_static/fonts/YanoneKaffeesatz-Regular.ttf b/docs/_static/fonts/YanoneKaffeesatz-Regular.ttf new file mode 100644 index 0000000..25cee81 Binary files /dev/null and b/docs/_static/fonts/YanoneKaffeesatz-Regular.ttf differ diff --git a/docs/_static/fonts/YanoneKaffeesatz-Thin.ttf b/docs/_static/fonts/YanoneKaffeesatz-Thin.ttf new file mode 100644 index 0000000..6e26d9e Binary files /dev/null and b/docs/_static/fonts/YanoneKaffeesatz-Thin.ttf differ diff --git a/docs/_static/haiku.css b/docs/_static/haiku.css new file mode 100644 index 0000000..615ed47 --- /dev/null +++ b/docs/_static/haiku.css @@ -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 + * Stephan Assmus + * Braden Ewing + * Humdinger + * + * :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; +} + diff --git a/docs/_static/header.png b/docs/_static/header.png new file mode 100644 index 0000000..2aaa53a Binary files /dev/null and b/docs/_static/header.png differ diff --git a/docs/_static/images/arch_layers.png b/docs/_static/images/arch_layers.png new file mode 100644 index 0000000..1ee183b Binary files /dev/null and b/docs/_static/images/arch_layers.png differ diff --git a/docs/_static/ir_black.css b/docs/_static/ir_black.css new file mode 100644 index 0000000..f04bc73 --- /dev/null +++ b/docs/_static/ir_black.css @@ -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 */ diff --git a/docs/_static/nature.css b/docs/_static/nature.css new file mode 100644 index 0000000..52b328e --- /dev/null +++ b/docs/_static/nature.css @@ -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; +} \ No newline at end of file diff --git a/docs/_static/noise_dk.png b/docs/_static/noise_dk.png new file mode 100644 index 0000000..f0b4de4 Binary files /dev/null and b/docs/_static/noise_dk.png differ diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css new file mode 100644 index 0000000..f04bc73 --- /dev/null +++ b/docs/_static/pygments.css @@ -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 */ diff --git a/docs/_static/sphinxdoc.css b/docs/_static/sphinxdoc.css new file mode 100644 index 0000000..0a42807 --- /dev/null +++ b/docs/_static/sphinxdoc.css @@ -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; +} diff --git a/docs/_templates/defindex.html b/docs/_templates/defindex.html new file mode 100644 index 0000000..ce8d3af --- /dev/null +++ b/docs/_templates/defindex.html @@ -0,0 +1,35 @@ +{# + basic/defindex.html + ~~~~~~~~~~~~~~~~~~~ + + Default template for the "index" page. + + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{% extends "layout.html" %} +{% set title = _('Overview') %} +{% block body %} +

{{ docstitle|e }}

+

+ Welcome! This is + {% block description %}the documentation for {{ project|e }} + {{ release|e }}{% if last_updated %}, last updated {{ last_updated|e }}{% endif %}{% endblock %}. +

+ {% block tables %} +

{{ _('Indices and tables:') }}

+ + +
+ + + + + +
+ {% endblock %} +{% endblock %} diff --git a/docs/_templates/indexcontent.html b/docs/_templates/indexcontent.html new file mode 100644 index 0000000..d5e17cd --- /dev/null +++ b/docs/_templates/indexcontent.html @@ -0,0 +1,61 @@ +{% extends "defindex.html" %} +{% block tables %} +

Parts of the documentation:

+ + +
+ + + + + + + + + + + + + +
+ +

Indices and tables:

+ + +
+ + + + + + +
+ +

Meta information:

+ + +
+ + + + + +
+{% endblock %} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..a5dd7c8 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,69 @@ +{# + haiku/layout.html + ~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the haiku theme. + + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{% extends "basic/layout.html" %} +{% set script_files = script_files + ['_static/theme_extras.js'] %} +{% set css_files = css_files + ['_static/print.css'] %} + +{# do not display relbars #} +{% block relbar1 %}{% endblock %} +{% block relbar2 %}{% endblock %} + +{% macro nav() %} +

+ {%- block haikurel1 %} + {%- endblock %} + {%- if prev %} + «  {{ prev.title }} +   ::   + {%- endif %} + {{ _('Contents') }} + {%- if next %} +   ::   + {{ next.title }}  » + {%- endif %} + {%- block haikurel2 %} + {%- endblock %} +

+{% endmacro %} + +{% block content %} +
+ {%- block haikuheader %} + {%- if theme_full_logo != "false" %} + + + + {%- else %} + {%- if logo -%} + + {%- endif -%} +

+ {{ project|e }} +

+

{{ shorttitle|e }}

+ {%- endif %} + {%- endblock %} +
+
+ {{ nav() }} +
+
+ {#{%- if display_toc %} +
+

Table Of Contents

+ {{ toc }} +
+ {%- endif %}#} + {% block body %}{% endblock %} +
+
+ {{ nav() }} +
+{% endblock %} diff --git a/docs/api/clientxmpp.rst b/docs/api/clientxmpp.rst new file mode 100644 index 0000000..1c5055a --- /dev/null +++ b/docs/api/clientxmpp.rst @@ -0,0 +1,19 @@ +========== +clientxmpp +========== + +.. module:: sleekxmpp.clientxmpp + +.. autodata:: SRV_SUPPORT + +.. autoclass:: ClientXMPP + + .. automethod:: connect + + .. automethod:: register_feature + + .. automethod:: get_roster + + .. automethod:: update_roster + + .. automethod:: del_roster_item diff --git a/docs/architecture.rst b/docs/architecture.rst new file mode 100644 index 0000000..53c326e --- /dev/null +++ b/docs/architecture.rst @@ -0,0 +1,269 @@ +.. 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 +------------------------- +``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 `. +The class also provides a basic eventing system which can be triggered +either manually or on a timed schedule. + +The Main Threads +~~~~~~~~~~~~~~~~ +``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 `. + +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 + + + Hej! + + + +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 `. 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 ``Message`` :term:`stanza object` + because the namespaced element name ``{jabber:client}message`` is + associated with the 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 ``Message`` object is thus paired with the message stanza handler + ``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 + 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 + ``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 `, + these functions are referred to as :term:`event handlers `. + 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 ``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 `. + 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 ``XMLStream`` attempts to shy away from anything too XMPP specific, +``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 +``BaseXMPP``. + +.. index:: ClientXMPP, BaseXMPP + +ClientXMPP +---------- +``ClientXMPP`` extends ``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 +------------- +``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that +implements the component handshake protocol. + +.. index:: + double: object; stanza + +Stanza Objects: A Brief Look +---------------------------- +.. seealso:: + See :ref:`api-stanza-objects` for a more detailed overview. + +Almost worthy of their own standalone library, :term:`stanza objects ` +are wrappers for XML objects which expose dictionary like interfaces +for manipulating their XML content. For example, consider the XML: + +.. code-block:: xml + + + +A very plain element to start with, but we can create a :term:`stanza object` +using ``sleekxmpp.stanza.Message`` as so:: + + msg = Message(xml=ET.fromstring("")) + +The ``Message`` stanza class defines interfaces such as ``'body'`` and +``'to'``, so we can assign values to those interfaces to include new XML +content:: + + msg['body'] = "Following so far?" + msg['to'] = 'user@example.com' + +Dumping the XML content of ``msg`` (using ``msg.xml``), we find: + +.. code-block:: xml + + + Following so far? + + +The process is similar for reading from interfaces and deleting interface +contents. A :term:`stanza object` behaves very similarly to a regular +``dict`` object: you may assign to keys, read from keys, and ``del`` keys. + +Stanza interfaces come with built-in behaviours such as adding/removing +attribute and sub element values. However, a lot of the time more custom +logic is needed. This can be provided by defining methods of the form +``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom +behaviour. + +Stanza Plugins +~~~~~~~~~~~~~~ +Since it is generally possible to embed one XML element inside another, +:term:`stanza objects ` may be nested. Nested +:term:`stanza objects ` are referred to as :term:`stanza plugins ` +or :term:`substanzas `. + +A :term:`stanza plugin` exposes its own interfaces by adding a new +interface to its parent stanza. To demonstrate, consider these two stanza +class definitions using ``sleekxmpp.xmlstream.ElementBase``: + + +.. code-block:: python + + class Parent(ElementBase): + name = "the-parent-xml-element-name" + namespace = "the-parent-namespace" + interfaces = set(('foo', 'bar')) + + class Child(ElementBase): + name = "the-child-xml-element-name" + namespace = "the-child-namespace" + plugin_attrib = 'child' + interfaces = set(('baz',)) + + +If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as +so, using ``sleekxmpp.xmlstream.register_stanza_plugin``:: + + register_stanza_plugin(Parent, Child) + +Then we can access content in the child stanza through the parent. +Note that the interface used to access the child stanza is the same as +``Child.plugin_attrib``:: + + parent = Parent() + parent['foo'] = 'a' + parent['child']['baz'] = 'b' + +The above code would produce: + +.. code-block:: xml + + + + + +It is also possible to allow a :term:`substanza` to appear multiple times +by using ``iterable=True`` in the ``register_stanza_plugin`` call. All +iterable :term:`substanzas ` can be accessed using a standard +``substanzas`` interface. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..8a16587 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,220 @@ +# -*- 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'] + +# 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.0RC1' + +# 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 = 'default' + +# 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 = 'haiku' + +# 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 +# " v 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 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) +] diff --git a/docs/create_plugin.rst b/docs/create_plugin.rst new file mode 100644 index 0000000..112ef50 --- /dev/null +++ b/docs/create_plugin.rst @@ -0,0 +1,677 @@ +Creating a SleekXMPP Plugin +=========================== + +One of the goals of SleekXMPP is to provide support for every draft or final +XMPP extension (`XEP `_). 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 `_. In-band registration +was implemented in example 14-6 (page 223) of `XMPP: The Definitive +Guide `_ 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 `, +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 `_. 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 `_ +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 + + + + + + + + +**Registration Form Request from an Existing User:** + + .. code-block:: xml + + + + + Foo + hunter2 + + + +**Unregister Account:** + + .. code-block:: xml + + + + + +**Incomplete Registration:** + + .. code-block:: xml + + + + Foo + + + + + + +**Conflicting Registrations:** + + .. code-block:: xml + + + + Foo + hunter2 + + + + + + +**Successful Registration:** + + .. code-block:: xml + + + + + +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() diff --git a/docs/event_index.rst b/docs/event_index.rst new file mode 100644 index 0000000..2c5dfd3 --- /dev/null +++ b/docs/event_index.rst @@ -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 ` + + 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 ` + + Signal that an initial presence stanza has been written to the XML stream. + + session_end + - **Data:** ``{}`` + - **Source:** :py:class:`ClientXMPP `, + :py:class:`ComponentXMPP ` + :py:class:`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 `_ + 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 `, + :py:class:`ComponentXMPP ` + :py:class:`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` diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 0000000..35d2dc8 --- /dev/null +++ b/docs/glossary.rst @@ -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` diff --git a/docs/guide_xep_0030.rst b/docs/guide_xep_0030.rst new file mode 100644 index 0000000..cb8d7d2 --- /dev/null +++ b/docs/guide_xep_0030.rst @@ -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 `_, 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 `_, 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 `_, 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 `_ 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) diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..3b9e829 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,130 @@ +SleekXMPP +######### + +.. sidebar:: Get the Code + + .. code-block:: sh + + pip install sleekxmpp + + The latest source code for SleekXMPP may be found on `Github + `_. Releases can be found in the + ``master`` branch, while the latest development version is in the + ``develop`` branch. + + **Stable Releases** + - `1.0 Beta6.1 `_ + - `1.0 Beta5 `_ + - `1.0 Beta4 `_ + - `1.0 Beta3 `_ + - `1.0 Beta2 `_ + - `1.0 Beta1 `_ + + **Develop Releases** + - `Latest Develop Version `_ + - `External Roster (Based on Develop Version) `_ + + + A mailing list and XMPP chat room are available for discussing and getting + help with SleekXMPP. + + **Mailing List** + http://groups.google.com/group/sleekxmpp-discussion + + **Chat** + `sleek@conference.jabber.org `_ + + +SleekXMPP is an :ref:`MIT licensed ` XMPP library for Python 2.6/3.1+, +and is featured in examples in +`XMPP: The Definitive Guide `_ +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. + +SleekXMPP Architecture and Design +--------------------------------- +.. toctree:: + :maxdepth: 2 + + architecture.rst + +Tutorials, FAQs, and How To Guides +---------------------------------- +.. toctree:: + :maxdepth: 2 + + quickstart + xmpp_tdg + create_plugin + guide_xep_0030 + + +API Reference +------------- +.. toctree:: + :maxdepth: 2 + + event_index + api/clientxmpp + +Additional Info +--------------- +.. toctree:: + :hidden: + + glossary + license + +* :ref:`license` +* :ref:`glossary` +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +Credits +------- +**Main Author:** Nathan Fritz + `fritzy@netflint.net `_, + `@fritzy `_ + + Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP + `_, and a member of the XMPP + Council. + +**Co-Author:** Lance Stout + `lancestout@gmail.com `_, + `@lancestout `_ + +**Contributors:** + - Brian Beggs (`macdiesel `_) + - Dann Martens (`dannmartens `_) + - Florent Le Coz (`louiz `_) + - Kevin Smith (`Kev `_, http://kismith.co.uk) + - Remko Tronçon (`remko `_, http://el-tramo.be) + - Te-jé Rogers (`te-je `_) + - Thom Nichols (`tomstrummer `_) + diff --git a/docs/license.rst b/docs/license.rst new file mode 100644 index 0000000..cbcf5c1 --- /dev/null +++ b/docs/license.rst @@ -0,0 +1,5 @@ +.. _license: + +License (MIT) +============= +.. include:: ../LICENSE diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..d97407a --- /dev/null +++ b/docs/make.bat @@ -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 ^` where ^ 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 diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..5994052 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,300 @@ +==================== +SleekXMPP Quickstart +==================== + +.. 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 `_ + or join the chat room at `sleek@conference.jabber.org + `_. + +If you have not yet installed SleekXMPP, do so now by either checking out a version +from `Github `_, 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 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 Boilerplate +---------------------------------- +As you wish: :ref:`the completed example `. + +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''' + +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 ` class +which we can extend to add our message echoing feature. :class:`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 `. +Calling :meth:`send_presence ` without any arguments will send the simplest +stanza allowed in XMPP: + +.. code-block:: xml + + + + +The second requirement is fulfilled using :meth:`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 ` 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 +` 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 +` is raised. Similarly, an :class:`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 + + + + + + + + + + + +Responding to Messages +~~~~~~~~~~~~~~~~~~~~~~ +Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and responding +to messages. The :term:`message` event is fired whenever a ```` 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`. + + +.. _echobot_complete: + +The Final Product +----------------- +Here then is the final result you should have after working through the guide above. + +.. code-block:: python + + #!/usr/bin/env python + # -*- coding: utf-8 -*- + import sys + import logging + import time + import getpass + from optparse import OptionParser + + import sleekxmpp + + # Python versions before 3.0 do not use UTF-8 encoding + # by default. To ensure that Unicode is handled properly + # throughout SleekXMPP, we will set the default encoding + # ourselves to UTF-8. + if sys.version_info < (3, 0): + reload(sys) + sys.setdefaultencoding('utf8') + + + class EchoBot(sleekxmpp.ClientXMPP): + + """ + A simple SleekXMPP bot that will echo messages it + receives, along with a short thank you message. + """ + + def __init__(self, jid, password): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can intialize + # our roster. + self.add_event_handler("session_start", self.start) + + # The message event is triggered whenever a message + # stanza is received. Be aware that that includes + # MUC messages and error messages. + self.add_event_handler("message", self.message) + + def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an intial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.send_presence() + self.get_roster() + + def message(self, msg): + """ + Process incoming message stanzas. Be aware that this also + includes MUC messages and error messages. It is usually + a good idea to check the messages's type before processing + or sending replies. + + Arguments: + msg -- The received message stanza. See the documentation + for stanza objects and the Message stanza to see + how it may be used. + """ + msg.reply("Thanks for sending\n%(body)s" % msg).send() + + + if __name__ == '__main__': + # Setup the command line arguments. + optp = OptionParser() + + # Output verbosity options. + optp.add_option('-q', '--quiet', help='set logging to ERROR', + action='store_const', dest='loglevel', + const=logging.ERROR, default=logging.INFO) + optp.add_option('-d', '--debug', help='set logging to DEBUG', + action='store_const', dest='loglevel', + const=logging.DEBUG, default=logging.INFO) + optp.add_option('-v', '--verbose', help='set logging to COMM', + action='store_const', dest='loglevel', + const=5, default=logging.INFO) + + # JID and password options. + 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() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = raw_input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + + # Setup the EchoBot and register plugins. Note that while plugins may + # have interdependencies, the order in which you register them does + # not matter. + xmpp = EchoBot(opts.jid, opts.password) + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0060') # PubSub + xmpp.register_plugin('xep_0199') # XMPP Ping + + # If you are working with an OpenFire server, you may need + # to adjust the SSL version used: + # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 + + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + # Connect to the XMPP server and start processing XMPP stanzas. + if xmpp.connect(): + # If you do not have the pydns library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(threaded=False) + print("Done") + else: + print("Unable to connect.") diff --git a/docs/xmpp_tdg.rst b/docs/xmpp_tdg.rst new file mode 100644 index 0000000..3d12b1b --- /dev/null +++ b/docs/xmpp_tdg.rst @@ -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 `_ +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 ` 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 `_ | +`View original code `_ + +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 ``

`` 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 = "

%(user)s: %(message)s

" % { + "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 `_ | +`View original code `_ + + +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 `_ | +`View original code `_ + + +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 `_ | +`View original code `_ + + +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 `_ | +`View original code `_ + +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 `_ | +`View original code `_