gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-survey] branch stable updated (332546c -> ff5502f)


From: gnunet
Subject: [GNUnet-SVN] [taler-survey] branch stable updated (332546c -> ff5502f)
Date: Fri, 29 Mar 2019 18:31:58 +0100

This is an automated email from the git hooks/post-receive script.

marcello pushed a change to branch stable
in repository survey.

 discard 332546c  hotfixing submodule upstream
 discard b69635c  Provide better link to install the wallet.
 discard 558cb86  Optimizing #5591.
 discard 335076f  Optimizing #5591.
 discard ae277a6  Optimizing #5591.
 discard 3f70070  Address #5591.
     add c3b8769  changelog
     add 5ae52f9  providing favicon
     add ba3265c  Restore back-office link
     add 8002091  Submodule remote
     add e9cd3b3  Update submodule
     add f9feea7  Doxyfile
     add 1b3258a  Instruct Doxygen to crawl recursively.
     add 0e49dbb  Doxygen-commenting the main parts.
     add 31afecd  fix comment
     add 6112e9d  Doxygen.
     add d71b77a  Addressing #5643.  Testing needed.
     add cf4f6cf  Export config base directory for tests.
     add d35b2de  Fix shebang.
     add 49c3ca5  Test and fix #5643; also updating the config module.
     add f40114e  fix pip3 invocation
     new 70caaf5  Address #5591.
     new 0bdaae8  Optimizing #5591.
     new cceffef  Optimizing #5591.
     new 2fb5c5b  Optimizing #5591.
     new ff5502f  Provide better link to install the wallet.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (332546c)
            \
             N -- N -- N   refs/heads/stable (ff5502f)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                     |   1 +
 ChangeLog                                      |   3 +
 Doxyfile                                       | 331 +++++++++++++++++++++
 Makefile.in                                    |   7 +-
 configure.ac                                   |  13 +
 setup.py                                       |   1 +
 taler-merchant-survey.in                       |  46 ++-
 talersurvey/survey/static/favicon.ico          |   1 +
 talersurvey/survey/survey.py                   |  82 +++++-
 talersurvey/survey/templates/base.html         |   1 +
 talersurvey/survey/templates/survey_stats.html |   2 +-
 talersurvey/talerconfig.py                     | 391 +++++++++++++++++++------
 talersurvey/tests.py                           |  21 ++
 13 files changed, 800 insertions(+), 100 deletions(-)
 create mode 100644 ChangeLog
 create mode 100644 Doxyfile
 create mode 100644 talersurvey/survey/static/favicon.ico

diff --git a/.gitignore b/.gitignore
index 87fffbc..67d89df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.eggs/
 *.pyc
 *.swp
 Makefile
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..ac4e04f
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,3 @@
+Tue Feb 13 10:16:17 CET 2018
+        Merchant frontend tipping users who answer
+        the survey.
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..518c651
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,331 @@
+# Doxyfile 1.8.13
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+DOXYFILE_ENCODING      = UTF-8
+PROJECT_NAME           = "taler-survey"
+PROJECT_NUMBER         =
+PROJECT_BRIEF          =
+PROJECT_LOGO           =
+OUTPUT_DIRECTORY       = doxygen-doc/
+CREATE_SUBDIRS         = NO
+ALLOW_UNICODE_NAMES    = NO
+OUTPUT_LANGUAGE        = English
+BRIEF_MEMBER_DESC      = YES
+REPEAT_BRIEF           = YES
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+ALWAYS_DETAILED_SEC    = NO
+INLINE_INHERITED_MEMB  = NO
+FULL_PATH_NAMES        = YES
+STRIP_FROM_PATH        =
+STRIP_FROM_INC_PATH    =
+SHORT_NAMES            = NO
+JAVADOC_AUTOBRIEF      = NO
+QT_AUTOBRIEF           = NO
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS           = YES
+SEPARATE_MEMBER_PAGES  = NO
+TAB_SIZE               = 4
+ALIASES                =
+TCL_SUBST              =
+OPTIMIZE_OUTPUT_FOR_C  = NO
+OPTIMIZE_OUTPUT_JAVA   = NO
+OPTIMIZE_FOR_FORTRAN   = NO
+OPTIMIZE_OUTPUT_VHDL   = NO
+EXTENSION_MAPPING      = in=Python
+MARKDOWN_SUPPORT       = YES
+TOC_INCLUDE_HEADINGS   = 0
+AUTOLINK_SUPPORT       = YES
+BUILTIN_STL_SUPPORT    = NO
+CPP_CLI_SUPPORT        = NO
+SIP_SUPPORT            = NO
+IDL_PROPERTY_SUPPORT   = YES
+DISTRIBUTE_GROUP_DOC   = NO
+GROUP_NESTED_COMPOUNDS = NO
+SUBGROUPING            = YES
+INLINE_GROUPED_CLASSES = NO
+INLINE_SIMPLE_STRUCTS  = NO
+TYPEDEF_HIDES_STRUCT   = NO
+LOOKUP_CACHE_SIZE      = 0
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL            = YES
+EXTRACT_PRIVATE        = NO
+EXTRACT_PACKAGE        = NO
+EXTRACT_STATIC         = NO
+EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_ANON_NSPACES   = NO
+HIDE_UNDOC_MEMBERS     = NO
+HIDE_UNDOC_CLASSES     = NO
+HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_IN_BODY_DOCS      = NO
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = YES
+HIDE_SCOPE_NAMES       = NO
+HIDE_COMPOUND_REFERENCE= NO
+SHOW_INCLUDE_FILES     = YES
+SHOW_GROUPED_MEMB_INC  = NO
+FORCE_LOCAL_INCLUDES   = NO
+INLINE_INFO            = YES
+SORT_MEMBER_DOCS       = YES
+SORT_BRIEF_DOCS        = NO
+SORT_MEMBERS_CTORS_1ST = NO
+SORT_GROUP_NAMES       = NO
+SORT_BY_SCOPE_NAME     = NO
+STRICT_PROTO_MATCHING  = NO
+GENERATE_TODOLIST      = YES
+GENERATE_TESTLIST      = YES
+GENERATE_BUGLIST       = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS       =
+MAX_INITIALIZER_LINES  = 30
+SHOW_USED_FILES        = YES
+SHOW_FILES             = YES
+SHOW_NAMESPACES        = NO
+FILE_VERSION_FILTER    =
+LAYOUT_FILE            =
+CITE_BIB_FILES         =
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET                  = NO
+WARNINGS               = YES
+WARN_IF_UNDOCUMENTED   = YES
+WARN_IF_DOC_ERROR      = YES
+WARN_NO_PARAMDOC       = NO
+WARN_AS_ERROR          = NO
+WARN_FORMAT            = "$file:$line: $text"
+WARN_LOGFILE           =
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT                  =
+INPUT_ENCODING         = UTF-8
+FILE_PATTERNS          = *.py *.in
+RECURSIVE              = YES
+EXCLUDE                = Makefile.in
+EXCLUDE_SYMLINKS       = NO
+EXCLUDE_PATTERNS       =
+EXCLUDE_SYMBOLS        =
+EXAMPLE_PATH           =
+EXAMPLE_PATTERNS       = *
+EXAMPLE_RECURSIVE      = NO
+IMAGE_PATH             =
+INPUT_FILTER           =
+FILTER_PATTERNS        =
+FILTER_SOURCE_FILES    = NO
+FILTER_SOURCE_PATTERNS =
+USE_MDFILE_AS_MAINPAGE =
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER         = NO
+INLINE_SOURCES         = NO
+STRIP_CODE_COMMENTS    = YES
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION    = NO
+REFERENCES_LINK_SOURCE = YES
+SOURCE_TOOLTIPS        = YES
+USE_HTAGS              = NO
+VERBATIM_HEADERS       = YES
+CLANG_ASSISTED_PARSING = NO
+CLANG_OPTIONS          =
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX     = YES
+COLS_IN_ALPHA_INDEX    = 5
+IGNORE_PREFIX          =
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML          = YES
+HTML_OUTPUT            = html
+HTML_FILE_EXTENSION    = .html
+HTML_HEADER            =
+HTML_FOOTER            =
+HTML_STYLESHEET        =
+HTML_EXTRA_STYLESHEET  =
+HTML_EXTRA_FILES       =
+HTML_COLORSTYLE_HUE    = 220
+HTML_COLORSTYLE_SAT    = 100
+HTML_COLORSTYLE_GAMMA  = 80
+HTML_TIMESTAMP         = NO
+HTML_DYNAMIC_SECTIONS  = NO
+HTML_INDEX_NUM_ENTRIES = 100
+GENERATE_DOCSET        = NO
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+DOCSET_PUBLISHER_NAME  = Publisher
+GENERATE_HTMLHELP      = NO
+CHM_FILE               =
+HHC_LOCATION           =
+GENERATE_CHI           = NO
+CHM_INDEX_ENCODING     =
+BINARY_TOC             = NO
+TOC_EXPAND             = NO
+GENERATE_QHP           = NO
+QCH_FILE               =
+QHP_NAMESPACE          = org.doxygen.Project
+QHP_VIRTUAL_FOLDER     = doc
+QHP_CUST_FILTER_NAME   =
+QHP_CUST_FILTER_ATTRS  =
+QHP_SECT_FILTER_ATTRS  =
+QHG_LOCATION           =
+GENERATE_ECLIPSEHELP   = NO
+ECLIPSE_DOC_ID         = org.doxygen.Project
+DISABLE_INDEX          = NO
+GENERATE_TREEVIEW      = NO
+ENUM_VALUES_PER_LINE   = 4
+TREEVIEW_WIDTH         = 250
+EXT_LINKS_IN_WINDOW    = NO
+FORMULA_FONTSIZE       = 10
+FORMULA_TRANSPARENT    = YES
+USE_MATHJAX            = NO
+MATHJAX_FORMAT         = HTML-CSS
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+MATHJAX_EXTENSIONS     =
+MATHJAX_CODEFILE       =
+SEARCHENGINE           = YES
+SERVER_BASED_SEARCH    = NO
+EXTERNAL_SEARCH        = NO
+SEARCHENGINE_URL       =
+SEARCHDATA_FILE        = searchdata.xml
+EXTERNAL_SEARCH_ID     =
+EXTRA_SEARCH_MAPPINGS  =
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX         = NO
+LATEX_OUTPUT           = latex
+LATEX_CMD_NAME         = latex
+MAKEINDEX_CMD_NAME     = makeindex
+COMPACT_LATEX          = NO
+PAPER_TYPE             = a4
+EXTRA_PACKAGES         =
+LATEX_HEADER           =
+LATEX_FOOTER           =
+LATEX_EXTRA_STYLESHEET =
+LATEX_EXTRA_FILES      =
+PDF_HYPERLINKS         = YES
+USE_PDFLATEX           = YES
+LATEX_BATCHMODE        = NO
+LATEX_HIDE_INDICES     = NO
+LATEX_SOURCE_CODE      = NO
+LATEX_BIB_STYLE        = plain
+LATEX_TIMESTAMP        = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF           = NO
+RTF_OUTPUT             = rtf
+COMPACT_RTF            = NO
+RTF_HYPERLINKS         = NO
+RTF_STYLESHEET_FILE    =
+RTF_EXTENSIONS_FILE    =
+RTF_SOURCE_CODE        = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN           = NO
+MAN_OUTPUT             = man
+MAN_EXTENSION          = .3
+MAN_SUBDIR             =
+MAN_LINKS              = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML           = NO
+XML_OUTPUT             = xml
+XML_PROGRAMLISTING     = YES
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+GENERATE_DOCBOOK       = NO
+DOCBOOK_OUTPUT         = docbook
+DOCBOOK_PROGRAMLISTING = NO
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF   = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD       = NO
+PERLMOD_LATEX          = NO
+PERLMOD_PRETTY         = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING   = YES
+MACRO_EXPANSION        = NO
+EXPAND_ONLY_PREDEF     = NO
+SEARCH_INCLUDES        = YES
+INCLUDE_PATH           =
+INCLUDE_FILE_PATTERNS  =
+PREDEFINED             =
+EXPAND_AS_DEFINED      =
+SKIP_FUNCTION_MACROS   = YES
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+TAGFILES               =
+GENERATE_TAGFILE       =
+ALLEXTERNALS           = NO
+EXTERNAL_GROUPS        = YES
+EXTERNAL_PAGES         = YES
+PERL_PATH              = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS         = NO
+MSCGEN_PATH            =
+DIA_PATH               =
+HIDE_UNDOC_RELATIONS   = YES
+HAVE_DOT               = YES
+DOT_NUM_THREADS        = 0
+DOT_FONTNAME           = Helvetica
+DOT_FONTSIZE           = 10
+DOT_FONTPATH           =
+CLASS_GRAPH            = YES
+COLLABORATION_GRAPH    = YES
+GROUP_GRAPHS           = YES
+UML_LOOK               = NO
+UML_LIMIT_NUM_FIELDS   = 10
+TEMPLATE_RELATIONS     = NO
+INCLUDE_GRAPH          = YES
+INCLUDED_BY_GRAPH      = YES
+CALL_GRAPH             = NO
+CALLER_GRAPH           = NO
+GRAPHICAL_HIERARCHY    = YES
+DIRECTORY_GRAPH        = YES
+DOT_IMAGE_FORMAT       = png
+INTERACTIVE_SVG        = NO
+DOT_PATH               =
+DOTFILE_DIRS           =
+MSCFILE_DIRS           =
+DIAFILE_DIRS           =
+PLANTUML_JAR_PATH      =
+PLANTUML_CFG_FILE      =
+PLANTUML_INCLUDE_PATH  =
+DOT_GRAPH_MAX_NODES    = 50
+MAX_DOT_GRAPH_DEPTH    = 0
+DOT_TRANSPARENT        = NO
+DOT_MULTI_TARGETS      = NO
+GENERATE_LEGEND        = YES
+DOT_CLEANUP            = YES
diff --git a/Makefile.in b/Makefile.in
index a879ae2..8f5ae5b 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -40,16 +40,17 @@ devinstall: $(templates) install-data
 # install into prefix
 .PHONY: install
 install: $(templates) install-data
-       @pip3 install . --install-option="address@hidden@"
+       @pip3 install . @DEBIAN_PIP3_SYSTEM@ --install-option="address@hidden@"
        @# force update when sources changed
-       @pip3 install . --install-option="address@hidden@" --upgrade --no-deps
+       @pip3 install . @DEBIAN_PIP3_SYSTEM@ --install-option="address@hidden@" 
--upgrade --no-deps
        cd talersurvey/survey/static/web-common && make install && cd -
 
 # run testcases
 .PHONY: check
 check:
        @export address@hidden@/talersurvey/tests.conf; \
-        python3 setup.py test
+        export address@hidden@; \
+         python3 setup.py test
 
 pylint:
        @pylint talersurvey/
diff --git a/configure.ac b/configure.ac
index 58f5b5e..5e349d2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -25,6 +25,19 @@ if test $? -ne 0;
   AC_MSG_ERROR([Please install pip3>=6.0])
 fi
 
+# On Debian systems, we may need to pass "--system" to pip3 to get
+# to the desired installation target directory
+pip3 install --help | grep '\-\-system' >> /dev/null
+if test $? -ne 0;
+then
+   DEBIAN_PIP3_SYSTEM=""
+else
+   DEBIAN_PIP3_SYSTEM="--system"
+fi
+AC_SUBST(DEBIAN_PIP3_SYSTEM)
+
+
+
 VERSION=$(pip3 --version | $AWK '{ print $2 }')
 
 AC_MSG_RESULT([$VERSION])
diff --git a/setup.py b/setup.py
index 2fa7539..50929e4 100755
--- a/setup.py
+++ b/setup.py
@@ -14,6 +14,7 @@ setup(name='talersurvey',
       package_data={
           '':[
               "survey/templates/*.html",
+              "survey/static/favicon.ico",
               "survey/static/*.svg",
               "survey/static/*.css",
               "survey/static/*.js",
diff --git a/taler-merchant-survey.in b/taler-merchant-survey.in
index ba82f03..4bb4eaf 100644
--- a/taler-merchant-survey.in
+++ b/taler-merchant-survey.in
@@ -1,9 +1,25 @@
 #!/usr/bin/env python3
 
-"""
-Stand-alone script to manage the GNU Taler
-survey frontend.
-"""
+##
+# This file is part of TALER
+# (C) 2017 INRIA
+#
+# TALER is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public
+# License as published by the Free Software Foundation; either
+# version 3, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty
+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with TALER; see the file COPYING.  If not,
+# see <http://www.gnu.org/licenses/>
+#
+#  @author Florian Dold
+#  @file Standalone script to launch the Survey site.
 
 import argparse
 import sys
@@ -12,18 +28,26 @@ import site
 import logging
 from talersurvey.talerconfig import TalerConfig
 
-
 os.environ.setdefault("TALER_PREFIX", "@prefix@")
 site.addsitedir("%s/lib/python%d.%d/site-packages" % (
     "@prefix@",
     sys.version_info.major,
     sys.version_info.minor))
 
+
+## @cond
 TC = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE"))
 LOGGER = logging.getLogger(__name__)
 # No perfect match to our logging format, but good enough ...
 UWSGI_LOGFMT = "%(ltime) %(proto) %(method) %(uri) %(proto) => %(status)"
+## @endcond
+
 
+##
+# This function interprets the 'serve-http' subcommand.
+# The effect it to launch the Survey site as a HTTP service.
+#
+# @param args command line options.
 def handle_serve_http(args):
     port = args.port
     if port is None:
@@ -36,6 +60,14 @@ def handle_serve_http(args):
               "--http", spec,
               "--wsgi-file", "@prefix@/share/taler/frontend-survey.wsgi")
 
+
+##
+# This function interprets the 'serve-uwsgi' subcommand.
+# The effect is to launch the Survey UWSGI service.  This
+# type of service is usually used when the HTTP Survey interface
+# is accessed via a reverse proxy (like Nginx, for example).
+#
+# @param command line options.
 def handle_serve_uwsgi(args):
     del args # pacify PEP checkers
     serve_uwsgi = 
TC["survey"]["uwsgi_serve"].value_string(required=True).lower()
@@ -58,7 +90,7 @@ def handle_serve_uwsgi(args):
     os.execlp(*params)
 
 
-
+## @cond
 PARSER = argparse.ArgumentParser()
 PARSER.set_defaults(func=None)
 PARSER.add_argument('--config', '-c',
@@ -75,6 +107,8 @@ P = SUB.add_parser('serve-uwsgi', help="Serve over UWSGI")
 P.set_defaults(func=handle_serve_uwsgi)
 
 ARGS = PARSER.parse_args()
+## @endcond
+
 if getattr(ARGS, 'func', None) is None:
     PARSER.print_help()
     sys.exit(1)
diff --git a/talersurvey/survey/static/favicon.ico 
b/talersurvey/survey/static/favicon.ico
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/talersurvey/survey/static/favicon.ico
@@ -0,0 +1 @@
+
diff --git a/talersurvey/survey/survey.py b/talersurvey/survey/survey.py
index 5126963..79d0f16 100644
--- a/talersurvey/survey/survey.py
+++ b/talersurvey/survey/survey.py
@@ -1,3 +1,4 @@
+##
 # This file is part of GNU TALER.
 # Copyright (C) 2017 Taler Systems SA
 #
@@ -13,8 +14,11 @@
 # GNU TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 #
 # @author Marcello Stanisci
+# @brief Minimal Website to tip users who fill the survey.
 
 import os
+import re
+import datetime
 import base64
 import logging
 import json
@@ -36,6 +40,13 @@ app.config.from_object(__name__)
 LOGGER = logging.getLogger(__name__)
 
 
+##
+# Helper function that returns a HTTP response with
+# a prettified version of a backend error response.
+#
+# @param requests_response the error response coming from
+#        the merchant backend.
+# @return a flask-native response object.
 def backend_error(requests_response):
     LOGGER.error("Backend error: status code: "
                  + str(requests_response.status_code))
@@ -47,18 +58,44 @@ def backend_error(requests_response):
         return flask.jsonify(dict(error="Backend died, no JSON got from it")), 
502
 
 
+##
+# Make the environment available into templates.
+#
+# @return the environment-reading function.
 @app.context_processor
 def utility_processor():
     def env(name, default=None):
         return os.environ.get(name, default)
-    return dict(env=env)
+    def prettydate(talerdate):
+       parsed_time = re.search(r"/Date\(([0-9]+)\)/", talerdate) 
+       if not parsed_time:
+           return "malformed date given"
+       parsed_time = int(parsed_time.group(1))
+       timestamp = datetime.datetime.fromtimestamp(parsed_time)
+       # returns the YYYY-MM-DD date format.
+       return timestamp.strftime("%Y-%b-%d")
 
+    return dict(env=env, prettydate=prettydate)
 
+
+##
+# Return a error response to the client.
+#
+# @param abort_status_code status code to return along the response.
+# @param params _kw_ arguments to passed verbatim to the templating engine.
 def err_abort(abort_status_code, **params):
     t = flask.render_template("templates/error.html", **params)
     flask.abort(flask.make_response(t, abort_status_code))
 
 
+##
+# POST a request to the backend, and return a error
+# response if any error occurs.
+#
+# @param endpoint the backend endpoint where to POST
+#        this request.
+# @param json the POST's body.
+# @return the backend response (JSON format).
 def backend_post(endpoint, json):
     headers = {"Authorization": "ApiKey " + APIKEY}
     try:
@@ -76,6 +113,13 @@ def backend_post(endpoint, json):
     return response_json
 
 
+##
+# Issue a GET request to the backend.
+#
+# @param endpoint the backend endpoint where to issue the request.
+# @param params (dict type of) URL parameters to append to the request.
+# @return the JSON response from the backend, or a error response
+#         if something unexpected happens.
 def backend_get(endpoint, params):
     headers = {"Authorization": "ApiKey " + APIKEY}
     try:
@@ -92,18 +136,47 @@ def backend_get(endpoint, params):
     return response_json
 
 
+##
+# Exception handler to capture all the unmanaged errors.
+#
+# @param e the Exception object, currently unused.
+# @return flask-native response object carrying the error message
+#         (and execution stack!).
 @app.errorhandler(Exception)
 def internal_error(e):
     return flask.render_template("templates/error.html",
                                  message="Internal error",
                                  stack=traceback.format_exc())
 
+
+##
+# Serve the /favicon.ico requests.
+#
+# @return the favicon.ico file.
address@hidden("/favicon.ico")
+def favicon():
+    print("will look into: " + os.path.join(app.root_path, 'static'))
+    return flask.send_from_directory(os.path.join(app.root_path, 'static'),
+                                     "favicon.ico", 
mimetype="image/vnd.microsoft.ico")
+
+##
+# Give information about the tip reserve status.
+#
+# @return the backend response to a /tip-query request,
+#         in a prettified format.
 @app.route("/survey-stats", methods=["GET"])
 def survey_stats():
     stats = backend_get("tip-query", dict(instance="default"))
     return flask.render_template("templates/survey_stats.html", stats=stats)
 
-
+##
+# Tell the backend to 'authorize' a tip; this means that
+# the backend will allocate a certain amount to be later
+# picked up by the wallet.
+#
+# @return the URL where to redirect the browser, in order
+#         for the wallet to pick the tip up, or a error page
+#         otherwise.
 @app.route("/submit-survey", methods=["POST"])
 def submit_survey():
     tip_spec = dict(amount=CURRENCY + ":1.0",
@@ -118,7 +191,10 @@ def submit_survey():
     err_abort(500, message="Tipping failed, unexpected backend response",
               json=resp)
 
-
+##
+# Serve the main index page.
+#
+# @return response object of the index page.
 @app.route("/", methods=["GET"])
 def index():
     return flask.render_template("templates/index.html", 
merchant_currency=CURRENCY)
diff --git a/talersurvey/survey/templates/base.html 
b/talersurvey/survey/templates/base.html
index 36b0650..fb5e59e 100644
--- a/talersurvey/survey/templates/base.html
+++ b/talersurvey/survey/templates/base.html
@@ -39,6 +39,7 @@
       <li><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}">Essay 
Shop</a></li>
       <li><a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') 
}}">Donations</a></li>
       <li><a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#') 
}}">Survey</a></li>
+      <li><a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') 
}}">Back-office</a></li>
     </ul>
     <p>You can learn more about Taler on our main <a 
href="https://taler.net";>website</a>.</p>
   </div>
diff --git a/talersurvey/survey/templates/survey_stats.html 
b/talersurvey/survey/templates/survey_stats.html
index 62c77dd..41cb437 100644
--- a/talersurvey/survey/templates/survey_stats.html
+++ b/talersurvey/survey/templates/survey_stats.html
@@ -6,7 +6,7 @@
     merchant.  Usually this should not be visible to users.</p>
     <ul>
     <li>Reserve pub: {{ stats['reserve_pub'] }}</li>
-    <li>Reserve expiration: {{ stats['reserve_expiration'] }}</li>
+    <li>Reserve expiration: {{ prettydate(stats['reserve_expiration']) }}</li>
     <li>Amount available {{ stats['amount_available'] }}</li>
     <li>Amount picked up {{ stats['amount_picked_up'] }}</li>
     <li>Amount authorized {{ stats['amount_authorized'] }}</li>
diff --git a/talersurvey/talerconfig.py b/talersurvey/talerconfig.py
index a7ca065..69d06a8 100644
--- a/talersurvey/talerconfig.py
+++ b/talersurvey/talerconfig.py
@@ -1,22 +1,21 @@
-#  This file is part of TALER
-#  (C) 2016 INRIA
+##
+# This file is part of TALER
+# (C) 2016 INRIA
 #
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free 
Software
+# Foundation; either version 3, or (at your option) any later version.
 #
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+# You should have received a copy of the GNU General Public License along with
+# TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 #
-#  @author Florian Dold
-
-"""
-Parse GNUnet-style configurations in pure Python
-"""
+# @author Florian Dold
+# @author Marcello Stanisci
+# @brief Parse GNUnet-style configurations in pure Python
 
 import logging
 import collections
@@ -24,12 +23,14 @@ import os
 import weakref
 import sys
 import re
+from typing import Callable, Any
 
 LOGGER = logging.getLogger(__name__)
 
 __all__ = ["TalerConfig"]
 
 TALER_DATADIR = None
+
 try:
     # not clear if this is a good idea ...
     from talerpaths import TALER_DATADIR as t
@@ -37,21 +38,34 @@ try:
 except ImportError:
     pass
 
+##
+# Exception class for a any configuration error.
 class ConfigurationError(Exception):
     pass
 
+##
+# Exception class for malformed strings having with parameter
+# expansion.
 class ExpansionSyntaxError(Exception):
     pass
 
-
-def expand(var, getter):
-    """
-    Do shell-style parameter expansion.
-    Supported syntax:
-    - ${X}
-    - ${X:-Y}
-    - $X
-    """
+##
+# Do shell-style parameter expansion.
+# Supported syntax:
+#  - ${X}
+#  - ${X:-Y}
+#  - $X
+#
+# @param var entire config value that might contain a parameter
+#        to expand.
+# @param getter function that is in charge of returning _some_
+#        value to be used in place of the parameter to expand.
+#        Typically, the replacement is searched first under the
+#        PATHS section of the current configuration, or (if not
+#        found) in the environment.
+#
+# @return the expanded config value.
+def expand(var: str, getter: Callable[[str], str]) -> str:
     pos = 0
     result = ""
     while pos != -1:
@@ -88,38 +102,25 @@ def expand(var, getter):
         result = result + replace
         pos = end
 
-
     return result + var[pos:]
 
-
-class OptionDict(collections.defaultdict):
-    def __init__(self, config, section_name):
-        self.config = weakref.ref(config)
-        self.section_name = section_name
-        super().__init__()
-    def __missing__(self, key):
-        entry = Entry(self.config(), self.section_name, key)
-        self[key] = entry
-        return entry
-    def __getitem__(self, chunk):
-        return super().__getitem__(chunk.lower())
-    def __setitem__(self, chunk, value):
-        super().__setitem__(chunk.lower(), value)
-
-
-class SectionDict(collections.defaultdict):
-    def __missing__(self, key):
-        value = OptionDict(self, key)
-        self[key] = value
-        return value
-    def __getitem__(self, chunk):
-        return super().__getitem__(chunk.lower())
-    def __setitem__(self, chunk, value):
-        super().__setitem__(chunk.lower(), value)
-
-
+##
+# A configuration entry.
 class Entry:
-    def __init__(self, config, section, option, **kwargs):
+
+    ##
+    # Init constructor.
+    #
+    # @param self the object itself.
+    # @param config reference to a configuration object - FIXME
+    #        define "configuration object".
+    # @param section name of the config section where this entry
+    #        got defined.
+    # @param option name of the config option associated with this
+    #        entry.
+    # @param kwargs keyword arguments that hold the value / filename
+    #        / line number of this current option.
+    def __init__(self, config, section: str, option: str, **kwargs) -> None:
         self.value = kwargs.get("value")
         self.filename = kwargs.get("filename")
         self.lineno = kwargs.get("lineno")
@@ -127,14 +128,36 @@ class Entry:
         self.option = option
         self.config = weakref.ref(config)
 
-    def __repr__(self):
+    ##
+    # XML representation of this entry.
+    #
+    # @param self the object itself.
+    # @return XML string holding all the relevant information
+    #         for this entry.
+    def __repr__(self) -> str:
         return "<Entry section=%s, option=%s, value=%s>" \
                % (self.section, self.option, repr(self.value),)
 
-    def __str__(self):
+    ##
+    # Return the value for this entry, as is.
+    #
+    # @param self the object itself.
+    # @return the config value.
+    def __str__(self) -> Any:
         return self.value
 
-    def value_string(self, default=None, required=False, warn=False):
+    ##
+    # Return entry value, accepting defaults.
+    #
+    # @param self the object itself
+    # @param default default value to return if none was found.
+    # @param required indicate whether the value was required or not.
+    #        If the value was required, but was not found, an exception
+    #        is found.
+    # @param warn if True, outputs a warning message if the value was
+    #        not found -- regardless of it being required or not.
+    # @return the value, or the given @a default, if not found.
+    def value_string(self, default=None, required=False, warn=False) -> str:
         if required and self.value is None:
             raise ConfigurationError("Missing required option '%s' in section 
'%s'" \
                                      % (self.option.upper(), 
self.section.upper()))
@@ -149,7 +172,16 @@ class Entry:
             return default
         return self.value
 
-    def value_int(self, default=None, required=False, warn=False):
+    ##
+    # Return entry value as a _int_.  Raise exception if the
+    # value is not convertible to a integer.
+    #
+    # @param self the object itself
+    # @param default currently ignored.
+    # @param required currently ignored.
+    # @param warn currently ignored.
+    # @return the value, or the given @a default, if not found.
+    def value_int(self, default=None, required=False, warn=False) -> int:
         value = self.value_string(default, warn, required)
         if value is None:
             return None
@@ -158,8 +190,13 @@ class Entry:
         except ValueError:
             raise ConfigurationError("Expected number for option '%s' in 
section '%s'" \
                                      % (self.option.upper(), 
self.section.upper()))
-
-    def _getsubst(self, key):
+    ##
+    # Fetch value to substitute to expansion variables.
+    #
+    # @param self the object itself.
+    # @param key the value's name to lookup.
+    # @return the value, if found, None otherwise.
+    def _getsubst(self, key: str) -> Any:
         value = self.config()["paths"][key].value
         if value is not None:
             return value
@@ -168,31 +205,136 @@ class Entry:
             return value
         return None
 
-    def value_filename(self, default=None, required=False, warn=False):
+    ##
+    # Fetch the config value that should be a filename,
+    # taking care of invoking the variable-expansion logic first.
+    #
+    # @param self the object itself.
+    # @param default currently ignored.
+    # @param required currently ignored.
+    # @param warn currently ignored.
+    # @return the (expanded) filename.
+    def value_filename(self, default=None, required=False, warn=False) -> str:
         value = self.value_string(default, required, warn)
         if value is None:
             return None
         return expand(value, self._getsubst)
 
-    def location(self):
+    ##
+    # Give the filename and line number of this config entry.
+    #
+    # @param self this object.
+    # @return <filename>:<linenumber>, or "<unknown>" if one
+    #         is not known.
+    def location(self) -> str:
         if self.filename is None or self.lineno is None:
             return "<unknown>"
         return "%s:%s" % (self.filename, self.lineno)
 
+##
+# Represent a section by inheriting from 'defaultdict'.
+class OptionDict(collections.defaultdict):
+
+    ##
+    # Init constructor.
+    #
+    # @param self the object itself
+    # @param config the "config" object -- typically a @a TalerConfig instance.
+    # @param section_name the section name to assign to this object.
+    def __init__(self, config, section_name: str) -> None:
+        self.config = weakref.ref(config)
+        self.section_name = section_name
+        super().__init__()
+
+    ##
+    # Logic to run when a non-existent key is dereferenced.
+    # Just create and return a empty config @a Entry.  Note
+    # that the freshly created entry will nonetheless put
+    # under the accessed key (that *does* become existent
+    # afterwards).
+    #
+    # @param self the object itself.
+    # @param key the key attempted to be accessed.
+    # @return the no-value entry.
+    def __missing__(self, key: str) -> Entry:
+        entry = Entry(self.config(), self.section_name, key)
+        self[key] = entry
+        return entry
+
+    ##
+    # Attempt to fetch one value from the object.
+    #
+    # @param self the object itself.
+    # @param chunk the key (?) that is tried to access.
+    # @return the object, if it exists, or a freshly created
+    #         (empty) one, if it doesn't exist.
+    def __getitem__(self, chunk: str) -> Entry:
+        return super().__getitem__(chunk.lower())
+
+    ##
+    # Set one value into the object.
+    #
+    # @param self the object itself.
+    # @param chunk key under which the value is going to be set.
+    # @param value value to set the @a chunk to.
+    def __setitem__(self, chunk: str, value: Entry) -> None:
+        super().__setitem__(chunk.lower(), value)
+
+##
+# Collection of all the (@a OptionDict) sections.
+class SectionDict(collections.defaultdict):
 
+    ##
+    # Automatically invoked when a missing section is
+    # dereferenced.  It creates the missing - empty - section.
+    #
+    # @param self the object itself.
+    # @param key the dereferenced section name.
+    # @return the freshly created section.
+    def __missing__(self, key):
+        value = OptionDict(self, key)
+        self[key] = value
+        return value
+
+    ##
+    # Attempt to retrieve a section.
+    #
+    # @param self the object itself.
+    # @param chunk the section name.
+    def __getitem__(self, chunk: str) -> OptionDict:
+        return super().__getitem__(chunk.lower())
+
+    ##
+    # Set a section.
+    #
+    # @param self the object itself.
+    # @param chunk the section name to set.
+    # @param value the value to set under that @a chunk.
+    def __setitem__(self, chunk: str, value: OptionDict) -> None:
+        super().__setitem__(chunk.lower(), value)
+
+##
+# One loaded taler configuration, including base configuration
+# files and included files.
 class TalerConfig:
-    """
-    One loaded taler configuration, including base configuration
-    files and included files.
-    """
-    def __init__(self):
-        """
-        Initialize an empty configuration
-        """
-        self.sections = SectionDict()
-
-    # defaults != config file: the first is the 'base'
-    # whereas the second overrides things from the first.
+
+    ##
+    # Init constructor..
+    #
+    # @param self the object itself.
+    def __init__(self) -> None:
+        self.sections = SectionDict() # just plain dict
+
+    ##
+    # Load a configuration file, instantiating a config object.
+    #
+    # @param filename the filename where to load the configuration
+    #        from.  If None, it defaults "taler.conf".
+    # @param load_defaults if True, then defaults values are loaded
+    #        (from canonical directories like "<prefix>/share/config.d/taler/")
+    #        before the actual configuration file.  This latter then
+    #        can override some/all the defaults.
+    # @return the config object.
     @staticmethod
     def from_file(filename=None, load_defaults=True):
         cfg = TalerConfig()
@@ -204,22 +346,57 @@ class TalerConfig:
                 filename = os.path.expanduser("~/.config/taler.conf")
         if load_defaults:
             cfg.load_defaults()
-        cfg.load_file(filename)
+        cfg.load_file(os.path.expanduser(filename))
         return cfg
 
-    def value_string(self, section, option, **kwargs):
+    ##
+    # Get a string value from the config.
+    #
+    # @param self the config object itself.
+    # @param section the section to fetch the value from.
+    # @param option the value's option name.
+    # @param kwargs dict argument with instructions about
+    #        the value retrieval logic.
+    # @return the wanted string (or a default / exception if
+    #         a error occurs).
+    def value_string(self, section, option, **kwargs) -> str:
         return self.sections[section][option].value_string(
             kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
 
-    def value_filename(self, section, option, **kwargs):
+    ##
+    # Get a value from the config that should be a filename.
+    # The variable expansion for the path's components is internally managed.
+    #
+    # @param self the config object itself.
+    # @param section the section to fetch the value from.
+    # @param option the value's option name.
+    # @param kwargs dict argument with instructions about
+    #        the value retrieval logic.
+    # @return the wanted filename (or a default / exception if
+    #         a error occurs).
+    def value_filename(self, section, option, **kwargs) -> str:
         return self.sections[section][option].value_filename(
             kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
 
-    def value_int(self, section, option, **kwargs):
+    ##
+    # Get a integer value from the config.
+    #
+    # @param self the config object itself.
+    # @param section the section to fetch the value from.
+    # @param option the value's option name.
+    # @param kwargs dict argument with instructions about
+    #        the value retrieval logic.
+    # @return the wanted integer (or a default / exception if
+    #         a error occurs).
+    def value_int(self, section, option, **kwargs) -> int:
         return self.sections[section][option].value_int(
             kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
 
-    def load_defaults(self):
+    ##
+    # Load default values from canonical locations.
+    #
+    # @param self the object itself.
+    def load_defaults(self) -> None:
         base_dir = os.environ.get("TALER_BASE_CONFIG")
         if base_dir:
             self.load_dir(base_dir)
@@ -236,16 +413,25 @@ class TalerConfig:
             return
         LOGGER.warning("no base directory found")
 
+    ##
+    # Load configuration from environment variable
+    # TALER_CONFIG_FILE or from default location if the
+    # variable is not set.
+    #
+    # @param args currently unused.
+    # @param kwargs kwargs for subroutine @a from_file.
+    # @return freshly instantiated config object.
     @staticmethod
     def from_env(*args, **kwargs):
-        """
-        Load configuration from environment variable TALER_CONFIG_FILE
-        or from default location if the variable is not set.
-        """
         filename = os.environ.get("TALER_CONFIG_FILE")
         return TalerConfig.from_file(filename, *args, **kwargs)
 
-    def load_dir(self, dirname):
+    ##
+    # Load config values from _each_ file found in a directory.
+    #
+    # @param self the object itself.
+    # @param dirname the directory to crawl in the look for config files.
+    def load_dir(self, dirname) -> None:
         try:
             files = os.listdir(dirname)
         except FileNotFoundError:
@@ -256,7 +442,11 @@ class TalerConfig:
                 continue
             self.load_file(os.path.join(dirname, file))
 
-    def load_file(self, filename):
+    ##
+    # Load config values from a file.
+    #
+    # @param filename config file to take the values from.
+    def load_file(self, filename) -> None:
         sections = self.sections
         try:
             with open(filename, "r") as file:
@@ -271,6 +461,16 @@ class TalerConfig:
                     if line.startswith("#"):
                         # comment
                         continue
+                    if line.startswith("@INLINE@"):
+                        pair = line.split()
+                        if 2 != len(pair):
+                            LOGGER.error("invalid inlined config filename 
given ('%s')" % line)
+                            continue 
+                        if pair[1].startswith("/"):
+                            self.load_file(pair[1])
+                        else:
+                            
self.load_file(os.path.join(os.path.dirname(filename), pair[1]))
+                        continue
                     if line.startswith("["):
                         if not line.endswith("]"):
                             LOGGER.error("invalid section header in line %s: 
%s",
@@ -299,8 +499,16 @@ class TalerConfig:
             LOGGER.error("Configuration file (%s) not found", filename)
             sys.exit(3)
 
-
-    def dump(self):
+    ##
+    # Dump the textual representation of a config object.
+    # 
+    # Format:
+    # 
+    # [section]
+    # option = value # FIXME (what is location?)
+    #
+    # @param self the object itself, that will be dumped.
+    def dump(self) -> None:
         for kv_section in self.sections.items():
             print("[%s]" % (kv_section[1].section_name,))
             for kv_option in kv_section[1].items():
@@ -309,7 +517,16 @@ class TalerConfig:
                        kv_option[1].value,
                        kv_option[1].location()))
 
-    def __getitem__(self, chunk):
+
+    ##
+    # Return a whole section from this object.
+    #
+    # @param self the object itself.
+    # @param chunk name of the section to return.
+    # @return the section - note that if the section is
+    #         not found, a empty one will created on the fly,
+    #         then set under 'chunk', and returned.
+    def __getitem__(self, chunk: str) -> OptionDict:
         if isinstance(chunk, str):
             return self.sections[chunk]
         raise TypeError("index must be string")
diff --git a/talersurvey/tests.py b/talersurvey/tests.py
index 7dc2446..d25da48 100644
--- a/talersurvey/tests.py
+++ b/talersurvey/tests.py
@@ -1,3 +1,21 @@
+##
+# This file is part of GNU TALER.
+# Copyright (C) 2017 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free 
Software
+# Foundation; either version 2.1, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+#
+# You should have received a copy of the GNU Lesser General Public License 
along with
+# GNU TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+# @author Marcello Stanisci
+# @brief Test cases for the Survey site.
+
 #!/usr/bin/env python3
 
 import os
@@ -9,6 +27,9 @@ from talersurvey.talerconfig import TalerConfig
 TC = TalerConfig.from_env()
 CURRENCY = TC["taler"]["currency"].value_string(required=True)
 
+
+##
+# Main class that gathers all the tests.
 class SurveyTestCase(unittest.TestCase):
     def setUp(self):
         survey.app.testing = True

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]