diff --git a/.gitignore b/.gitignore index df03c84..8832a03 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ sdist */config/networks.json */config/users.json + +#Docs +_build diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..5d3b36c --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,2 @@ +conda: + file: environment.yml diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..f5fe78d --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,225 @@ +# 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) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: 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 " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @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 " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +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." + +.PHONY: qthelp +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/ircb.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ircb.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ircb" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ircb" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +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)." + +.PHONY: latexpdf +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." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +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." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..deaeeeb --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,10 @@ +============= +API Reference +============= + +.. module:: ircb + +.. toctree:: + :maxdepth: 2 + + user diff --git a/docs/api/user.rst b/docs/api/user.rst new file mode 100644 index 0000000..fb395f2 --- /dev/null +++ b/docs/api/user.rst @@ -0,0 +1,9 @@ +User +---- + +.. currentmodule:: ircb + +Create an User +~~~~~~~~~~~~~~ + +.. autoclass:: ircb.web.user.SignupView diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..b2b4f7d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +# +# ircb documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 24 01:41:35 2017. +# +# 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 os +import pkg_resources +import sys + +sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('..')) + + +#TODO: Un-comment this when the readthedocs supports Python 3.5 +#__version__ = pkg_resources.get_distribution('ircb').version +__version__ = '0.3.0' + +is_rtd = os.environ.get('READTHEDOCS', None) == True + +# -- 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', + 'sphinxcontrib.httpdomain', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +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'ircb' +copyright = u'2017, Ratnadeep Debnath, Sayan Chowdhury' +author = u'Ratnadeep Debnath, Sayan Chowdhury' + +# 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 = __version__ +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +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. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# 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 = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +if not is_rtd: + html_theme = 'sphinx_rtd_theme' + +# 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 = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['_themes'] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'ircb v0' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# 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 (relative to this directory) to use as a 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'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# 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 + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ircbdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'ircb.tex', u'ircb Documentation', + u'Ratnadeep Debnath, Sayan Chowdhury', '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 + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# 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 = [ + (master_doc, 'ircb', u'ircb Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'ircb', u'ircb Documentation', + author, 'ircb', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..f7e7d24 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +========================================= +ircb - A versatile IRC bouncer for humans +========================================= + +Contents: + +.. toctree:: + :maxdepth: 1 + + install + api/index + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..d943ad7 --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,5 @@ +============ +Installation +============ + +This is the installation page. diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..a425a09 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +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. epub3 to make an epub3 + 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. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +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\ircb.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ircb.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" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + 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" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF 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" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + 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 +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..adb4e46 --- /dev/null +++ b/environment.yml @@ -0,0 +1,31 @@ +name: py35 +dependencies: + - openssl=1.0.2g=0 + - pip=8.1.1=py35_0 + - python=3.5.1=0 + - readline=6.2=2 + - setuptools=20.3=py35_0 + - sqlite=3.9.2=0 + - tk=8.5.18=0 + - wheel=0.29.0=py35_0 + - xz=5.0.5=1 + - zlib=1.2.8=0 + - pip: + - Flask==0.10.1 + - Flask-SQLAlchemy==2.0 + - Flask-User==0.6.11 + - SQLAlchemy==1.0.13 + - SQLAlchemy-Utils==0.30.15 + - irc3==0.9.3 + - alembic==0.8.3 + - WTForms-Alchemy==0.15.0 + - click==6.3 + - aioredis==0.2.4 + - tabulate==0.7.5 + - aiohttp==1.2.0 + - aiohttp-auth==0.1.1 + - aiohttp-session[secure]==0.5.0 + - aiozmq==0.7.1 + - msgpack-python==0.4.7 + - PyYAML==3.11 + - sphinxcontrib-httpdomain diff --git a/ircb/models/network.py b/ircb/models/network.py index 79554d6..3292c83 100644 --- a/ircb/models/network.py +++ b/ircb/models/network.py @@ -3,13 +3,13 @@ from hashlib import md5 -import sqlalchemy as sa - +from sqlalchemy import Column, String, Boolean, ForeignKey, Integer +from sqlalchemy import UniqueConstraint, DateTime from sqlalchemy_utils import ChoiceType, Choice +from ircb.config import settings from ircb.models.lib import Base, get_session from ircb.models.user import User -from ircb.config import settings NETWORK_STATUS_TYPES = ( ('0', 'Connecting'), @@ -37,46 +37,46 @@ def _create_access_token(user_id, network_name): class Network(Base): __tablename__ = 'networks' __table_args__ = ( - sa.UniqueConstraint('user_id', 'name'), + UniqueConstraint('user_id', 'name'), ) - id = sa.Column(sa.Integer, primary_key=True) - name = sa.Column(sa.String(255), nullable=False) - nickname = sa.Column(sa.String(20), nullable=False) - hostname = sa.Column(sa.String(100), nullable=False) - port = sa.Column(sa.Integer, nullable=False) - realname = sa.Column(sa.String(100), nullable=False, default='') - username = sa.Column(sa.String(50), nullable=False, default='') - password = sa.Column(sa.String(100), nullable=False, default='') - usermode = sa.Column(sa.String(1), nullable=False, default='0') - ssl = sa.Column(sa.Boolean(), default=False) - ssl_verify = sa.Column(ChoiceType(SSL_VERIFY_CHOICES), - default=Choice(*SSL_VERIFY_CHOICES[0])) - - access_token = sa.Column(sa.String(100), nullable=False, unique=True, - default=lambda context: _create_access_token( - context.current_parameters['user_id'], - context.current_parameters['name'])) - user_id = sa.Column( - sa.Integer(), sa.ForeignKey(User.id, ondelete='CASCADE'), + id = Column(Integer, primary_key=True) + name = Column(String(255), nullable=False) + nickname = Column(String(20), nullable=False) + hostname = Column(String(100), nullable=False) + port = Column(Integer, nullable=False) + realname = Column(String(100), nullable=False, default='') + username = Column(String(50), nullable=False, default='') + password = Column(String(100), nullable=False, default='') + usermode = Column(String(1), nullable=False, default='0') + ssl = Column(Boolean(), default=False) + ssl_verify = Column(ChoiceType(SSL_VERIFY_CHOICES), + default=Choice(*SSL_VERIFY_CHOICES[0])) + + access_token = Column(String(100), nullable=False, unique=True, + default=lambda context: _create_access_token( + context.current_parameters['user_id'], + context.current_parameters['name'])) + user_id = Column( + Integer(), ForeignKey(User.id, ondelete='CASCADE'), nullable=False) # Runtime fields - current_nickname = sa.Column(sa.String(20), nullable=True) - status = sa.Column(ChoiceType(NETWORK_STATUS_TYPES), - default=Choice(*NETWORK_STATUS_TYPES[3])) + current_nickname = Column(String(20), nullable=True) + status = Column(ChoiceType(NETWORK_STATUS_TYPES), + default=Choice(*NETWORK_STATUS_TYPES[3])) # Remote socket info - rhost = sa.Column(sa.String(100), nullable=True) - rport = sa.Column(sa.Integer(), nullable=True) + rhost = Column(String(100), nullable=True) + rport = Column(Integer(), nullable=True) # Local socket info - lhost = sa.Column(sa.String(100), nullable=True) - lport = sa.Column(sa.Integer(), nullable=True) + lhost = Column(String(100), nullable=True) + lport = Column(Integer(), nullable=True) # timestamps - created = sa.Column(sa.DateTime, default=datetime.datetime.utcnow) - last_updated = sa.Column(sa.DateTime, - default=datetime.datetime.utcnow) + created = Column(DateTime, default=datetime.datetime.utcnow) + last_updated = Column(DateTime, + default=datetime.datetime.utcnow) def create_access_token(self): return _create_access_token(self.user.id, self.name) diff --git a/ircb/models/user.py b/ircb/models/user.py index 000a172..84e3384 100644 --- a/ircb/models/user.py +++ b/ircb/models/user.py @@ -1,14 +1,26 @@ import datetime +from hashlib import md5 + from flask_user import UserMixin from sqlalchemy import Column, String, Unicode, Boolean, ForeignKey, Integer from sqlalchemy import DateTime from sqlalchemy.orm import relationship, backref from sqlalchemy_utils import PasswordType +from ircb.config import settings from ircb.models.lib import Base +def _create_access_token(email, username): + return md5('{}{}{}{}'.format( + settings.SECRET_KEY, + email, + username, + datetime.datetime.utcnow()).encode() + ).hexdigest() + + class User(Base, UserMixin): __tablename__ = 'users' id = Column(Integer, primary_key=True) @@ -35,6 +47,11 @@ class User(Base, UserMixin): created = Column(DateTime, default=datetime.datetime.utcnow) last_updated = Column(DateTime, default=datetime.datetime.utcnow) + access_token = Column(String(100), nullable=False, unique=True, + default=lambda context: _create_access_token( + context.current_parameters['email'], + context.current_parameters['username'])) + def to_dict(self, serializable=False): d = super().to_dict() d.pop('password') diff --git a/ircb/storeclient/base.py b/ircb/storeclient/base.py index df8beeb..822dbdd 100644 --- a/ircb/storeclient/base.py +++ b/ircb/storeclient/base.py @@ -38,8 +38,8 @@ def initialize(cls): dispatcher.register(cls._on_message, signal=signal) @classmethod - def get(cls, data, raw=False): - result = yield from cls._get(data) + async def get(cls, data, raw=False): + result = await cls._get(data) if isinstance(result, dict): result = cls.model.from_dict(result) if raw is False else result elif isinstance(result, tuple): @@ -50,27 +50,27 @@ def get(cls, data, raw=False): return result @classmethod - def create(cls, data, async=False, timeout=10): - result = yield from cls._create(data, async) + async def create(cls, data, isasync=False, timeout=10): + result = await cls._create(data, isasync) return cls.model(**result) @classmethod - def update(cls, data): - result = yield from cls._update(data) + async def update(cls, data): + result = await cls._update(data) return cls.model(**result) @classmethod - def delete(cls, data): - result = yield from cls._delete(data) + async def delete(cls, data): + result = await cls._delete(data) return result @classmethod - def create_or_update(cls, data): - result = yield from cls._create_or_update(data) + async def create_or_update(cls, data): + result = await cls._create_or_update(data) return cls.model.from_dict(result) @classmethod - def _get(cls, data): + async def _get(cls, data): task_id = cls.get_task_id(data) fut = asyncio.Future() @@ -81,11 +81,11 @@ def callback(signal, data, taskid=None): dispatcher.register(callback, signal=cls.GOT_SIGNAL) dispatcher.send(cls.GET_SIGNAL, data, taskid=task_id) - result = yield from fut + result = await fut return fut.result() @classmethod - def _create(cls, data, async=False): + async def _create(cls, data, isasync=False): task_id = cls.get_task_id(data) fut = asyncio.Future() @@ -96,11 +96,11 @@ def callback(signal, data, taskid=None): dispatcher.register(callback, signal=cls.CREATED_SIGNAL) dispatcher.send(cls.CREATE_SIGNAL, data, taskid=task_id) - result = yield from fut + result = await fut return fut.result() @classmethod - def _update(cls, data): + async def _update(cls, data): task_id = cls.get_task_id(data) future = asyncio.Future() @@ -110,11 +110,11 @@ def callback(signal, data, taskid=None): dispatcher.register(callback, signal=cls.UPDATED_SIGNAL) dispatcher.send(cls.UPDATE_SIGNAL, data, taskid=task_id) - result = yield from future + result = await future return result @classmethod - def _delete(cls, data): + async def _delete(cls, data): task_id = cls.get_task_id(data) future = asyncio.Future() @@ -124,11 +124,11 @@ def callback(signal, data, taskid=None): dispatcher.register(callback, signal=cls.DELETED_SIGNAL) dispatcher.send(cls.DELETE_SIGNAL, data, taskid=task_id) - result = yield from future + result = await future return result @classmethod - def _create_or_update(cls, data, async=False): + async def _create_or_update(cls, data, isasync=False): task_id = cls.get_task_id(data) fut = asyncio.Future() @@ -140,7 +140,7 @@ def callback(signal, data, taskid=None): dispatcher.register(callback, signal=cls.UPDATED_SIGNAL) dispatcher.send(cls.CREATE_OR_UPDATE_SIGNAL, data, taskid=task_id) - result = yield from fut + result = await fut return fut.result() @classmethod diff --git a/ircb/stores/user.py b/ircb/stores/user.py index ea9e489..39fa57f 100644 --- a/ircb/stores/user.py +++ b/ircb/stores/user.py @@ -38,8 +38,14 @@ def get(cls, query): return session.query(User).get(query) @classmethod - def create(cls, username, email, password): - user = User(username=username, email=email, password=password) + def create(cls, username, email, password, first_name, last_name): + user = User( + username=username, + email=email, + password=password, + first_name=first_name, + last_name=last_name + ) session.add(user) session.commit() return user diff --git a/ircb/web/app.py b/ircb/web/app.py index 8c34804..8a89d6e 100644 --- a/ircb/web/app.py +++ b/ircb/web/app.py @@ -21,8 +21,7 @@ def index(request): return web.Response(body=b"Hello, ircb!") -@asyncio.coroutine -def init(loop, host='0.0.0.0', port=10000): +async def init(loop, host='0.0.0.0', port=10000): from ircb.storeclient import initialize initialize() load_config() @@ -46,7 +45,7 @@ def init(loop, host='0.0.0.0', port=10000): app.router.add_route('PUT', '/api/v1/network/{id}/{action}', NetworkConnectionView, name='network_connection') - srv = yield from loop.create_server( + srv = await loop.create_server( app.make_handler(logger=logger, access_log=logger), host, port) return srv diff --git a/ircb/web/constants.py b/ircb/web/constants.py new file mode 100644 index 0000000..d7687d0 --- /dev/null +++ b/ircb/web/constants.py @@ -0,0 +1 @@ +STATUS_OK = 200 diff --git a/ircb/web/user.py b/ircb/web/user.py index 6561f06..fb0f668 100644 --- a/ircb/web/user.py +++ b/ircb/web/user.py @@ -7,48 +7,68 @@ from ircb.storeclient import UserStore from ircb.web.decorators import auth_required +from ircb.web.constants import STATUS_OK from ircb.forms.user import UserForm class SignupView(web.View): + """ + .. http:post:: /api/v1/signup/ - @asyncio.coroutine - def post(self): - username = yield from auth.get_auth(self.request) + :string username: **Required**. The username of the user. + :string email: **Required**. The email of the user. + :string password: **Required**. The password of the user. + :string first_name: The first name of the user. + :string last_name: The last name of the user. + """ + + async def post(self): + username = await auth.get_auth(self.request) if username: raise web.HTTPForbidden() - data = yield from self.request.post() + data = await self.request.post() form = UserForm(formdata=data) form.validate() - yield from self._validate_username(form) - yield from self._validate_email(form) + await self._validate_username(form) + await self._validate_email(form) if form.errors: return web.Response(body=json.dumps(form.errors).encode(), status=400, content_type='application/json') cleaned_data = form.data - yield from UserStore.create( + user = await UserStore.create( dict( username=cleaned_data['username'], email=cleaned_data['email'], - password=cleaned_data['password'], + password=cleaned_data.get('password', ''), first_name=cleaned_data.get('first_name', ''), last_name=cleaned_data.get('last_name', '') ) ) - return web.Response(body=b'OK') + resp = { + 'status': STATUS_OK, + 'content_type': 'application/json', + 'charset': 'utf-8', + } + + resp.update({'body': json.dumps({ + 'username': user.username, + 'access_token': user.access_token, + }).encode()}) - def _validate_username(self, form): + return web.Response(**resp) + + async def _validate_username(self, form): username = form.username.data - users = yield from UserStore.get( + users = await UserStore.get( dict(query=('username', username))) if users: error_msg = 'Username already in use.' form.username.errors.append(error_msg) - def _validate_email(self, form): + async def _validate_email(self, form): email = form.email.data - users = yield from UserStore.get( + users = await UserStore.get( dict(query=('email', email))) if users: error_msg = 'Email already in use.' @@ -56,15 +76,41 @@ def _validate_email(self, form): class SigninView(web.View): + """ + .. http:post:: /api/v1/signin/ + + :string username: **Required**. The username of the user. + :string password: **Required**. The password of the user. + + If the header contains the `access_token` then password is not + required. + """ + + async def post(self): + token = self.request.headers.get('Authorization') + + data = await self.request.post() + username = data.get('username') + password = data.get('password') + + if password is None and token is None: + raise web.HTTPForbidden() + + if password is not None: + user = await UserStore.get( + dict(query=('auth', + (username, password)) + ) + ) + elif access_token is not None: + user = await UserStore.get( + dict(query=('auth', + (username, access_token)) + ) + ) - @asyncio.coroutine - def post(self): - data = yield from self.request.post() - user = yield from UserStore.get( - dict(query=('auth', (data.get('username'), data.get('password')))) - ) if user: - yield from auth.remember(self.request, user.username) + await auth.remember(self.request, user.username) return web.Response(body=b'OK') raise web.HTTPForbidden() diff --git a/requirements.txt b/requirements.txt index 2f57f4e..3768e5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Flask==0.10.1 Flask-SQLAlchemy==2.0 -Flask-User==0.6.4 +Flask-User==0.6.11 SQLAlchemy==1.0.13 SQLAlchemy-Utils==0.30.15 irc3==0.9.3 @@ -9,9 +9,10 @@ WTForms-Alchemy==0.15.0 click==6.3 aioredis==0.2.4 tabulate==0.7.5 -aiohttp==0.20.2 +aiohttp==1.2.0 aiohttp-auth==0.1.1 aiohttp-session[secure]==0.5.0 aiozmq==0.7.1 msgpack-python==0.4.7 PyYAML==3.11 +sphinxcontrib-httpdomain