From b5a2e4a12ef672896a35c6fadc900b66bb48688d Mon Sep 17 00:00:00 2001 From: Andre Anjos <andre.dos.anjos@gmail.com> Date: Fri, 25 Jan 2019 15:34:27 +0100 Subject: [PATCH] Implements new project creation closes #3 and #6 --- MANIFEST.in | 3 +- bob/devtools/scripts/new.py | 180 +++++ bob/devtools/templates/.gitignore | 20 + bob/devtools/templates/.gitlab-ci.yml | 1 + bob/devtools/templates/COPYING | 674 ++++++++++++++++++ bob/devtools/templates/LICENSE | 27 + bob/devtools/templates/MANIFEST.in | 2 + bob/devtools/templates/README.rst | 46 ++ bob/devtools/templates/buildout.cfg | 14 + bob/devtools/templates/conda/meta.yaml | 52 ++ bob/devtools/templates/doc/.DS_Store | Bin 0 -> 6148 bytes bob/devtools/templates/doc/conf.py | 254 +++++++ .../templates/doc/img/beat-128x128.png | Bin 0 -> 25056 bytes .../templates/doc/img/beat-favicon.ico | Bin 0 -> 27438 bytes bob/devtools/templates/doc/img/beat-logo.png | Bin 0 -> 13399 bytes .../templates/doc/img/bob-128x128.png | Bin 0 -> 6547 bytes .../templates/doc/img/bob-favicon.ico | Bin 0 -> 4286 bytes bob/devtools/templates/doc/img/bob-logo.png | Bin 0 -> 6266 bytes bob/devtools/templates/doc/index.rst | 23 + bob/devtools/templates/doc/links.rst | 8 + bob/devtools/templates/pkg/__init__.py | 3 + bob/devtools/templates/requirements.txt | 2 + bob/devtools/templates/setup.py | 66 ++ bob/devtools/templates/version.txt | 1 + conda/meta.yaml | 2 + doc/index.rst | 1 + doc/templates.rst | 523 ++++++++++++++ setup.py | 2 + 28 files changed, 1903 insertions(+), 1 deletion(-) create mode 100644 bob/devtools/scripts/new.py create mode 100644 bob/devtools/templates/.gitignore create mode 100644 bob/devtools/templates/.gitlab-ci.yml create mode 100644 bob/devtools/templates/COPYING create mode 100644 bob/devtools/templates/LICENSE create mode 100644 bob/devtools/templates/MANIFEST.in create mode 100644 bob/devtools/templates/README.rst create mode 100644 bob/devtools/templates/buildout.cfg create mode 100644 bob/devtools/templates/conda/meta.yaml create mode 100644 bob/devtools/templates/doc/.DS_Store create mode 100644 bob/devtools/templates/doc/conf.py create mode 100644 bob/devtools/templates/doc/img/beat-128x128.png create mode 100644 bob/devtools/templates/doc/img/beat-favicon.ico create mode 100644 bob/devtools/templates/doc/img/beat-logo.png create mode 100644 bob/devtools/templates/doc/img/bob-128x128.png create mode 100644 bob/devtools/templates/doc/img/bob-favicon.ico create mode 100644 bob/devtools/templates/doc/img/bob-logo.png create mode 100644 bob/devtools/templates/doc/index.rst create mode 100644 bob/devtools/templates/doc/links.rst create mode 100644 bob/devtools/templates/pkg/__init__.py create mode 100644 bob/devtools/templates/requirements.txt create mode 100644 bob/devtools/templates/setup.py create mode 100644 bob/devtools/templates/version.txt create mode 100644 doc/templates.rst diff --git a/MANIFEST.in b/MANIFEST.in index 86a6e92e..64a24146 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE README.rst buildout.cfg version.txt -recursive-include doc conf.py *.rst *.sh +recursive-include doc conf.py *.rst *.sh *.png *.ico recursive-include bob/devtools/data *.md *.yaml *.pem matplotlibrc +recursive-include bob/devtools/templates conf.py *.rst *.png *.ico LICENSE COPYING .gitlab-ci.yml .gitignore *.cfg *.txt *.py diff --git a/bob/devtools/scripts/new.py b/bob/devtools/scripts/new.py new file mode 100644 index 00000000..c6f7b8e2 --- /dev/null +++ b/bob/devtools/scripts/new.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +import os +import shutil +import logging +import datetime +logger = logging.getLogger(__name__) + +import click +import jinja2 +import pkg_resources + +from . import bdt +from ..log import verbosity_option + + +def copy_file(template, output_dir): + '''Copies a file from the template directory to the output directory + + Args: + + template: The path to the template, from the internal templates directory + output_dir: Where to save the output + ''' + + template_file = pkg_resources.resource_filename(__name__, os.path.join('..', + 'templates', template)) + output_file = os.path.join(output_dir, template) + + basedir = os.path.dirname(output_file) + if not os.path.exists(basedir): + logger.info('mkdir %s', basedir) + os.makedirs(basedir) + + logger.info('cp -a %s %s', template_file, output_file) + shutil.copy2(template_file, output_file) + + +def render_template(jenv, template, context, output_dir): + '''Renders a template to the output directory using specific context + + Args: + + jenv: The Jinja2 environment to use for rendering the template + template: The path to the template, from the internal templates directory + context: A dictionary with the context to render the template with + output_dir: Where to save the output + ''' + + output_file = os.path.join(output_dir, template) + + basedir = os.path.dirname(output_file) + if not os.path.exists(basedir): + logger.info('mkdir %s', basedir) + os.makedirs(basedir) + + with open(output_file, 'wt') as f: + logger.info('rendering %s', output_file) + T = jenv.get_template(template) + f.write(T.render(**context)) + + +@click.command(epilog=''' +Examples: + + 1. Generates a new project for Bob: + + $ bdt new -vv bob/bob.newpackage "John Doe" "joe@example.com" +''') +@click.argument('package') +@click.argument('author') +@click.argument('email') +@click.option('-t', '--title', show_default=True, + default='New package', help='This entry defines the package title. ' \ + 'The package title should be a few words only. It will appear ' \ + 'at the description of your package and as the title of your ' \ + 'documentation') +@click.option('-l', '--license', type=click.Choice(['bsd', 'gplv3']), + default='gplv3', show_default=True, + help='Changes the default licensing scheme to use for your package') +@click.option('-o', '--output-dir', help='Directory where to dump the new ' \ + 'project - must not exist') +@verbosity_option() +@bdt.raise_on_error +def new(package, author, email, title, license, output_dir): + """Creates a folder structure for a new Bob/BEAT package + """ + + if '/' not in package: + raise RuntimeError('PACKAGE should be specified as "group/name"') + + group, name = package.split('/') + + # creates the rst title, which looks like this: + # ======= + # Title + # ======= + rst_title = ('=' * (2+len(title))) + '\n ' + title + '\n' + \ + ('=' * (2+len(title))) + + # the jinja context defines the substitutions to be performed + today = datetime.datetime.today() + context = dict( + package = package, + group = group, + name = name, + author = author, + email = email, + title = title, + rst_title = rst_title, + license = license, + year = today.strftime('%Y'), + date = today.strftime('%c'), + ) + + # copy the whole template structure and de-templatize the needed files + if output_dir is None: + output_dir = os.path.join(os.path.realpath(os.curdir), name) + logger.info('Creating structure for %s at directory %s', package, + output_dir) + + if os.path.exists(output_dir): + raise IOError('The package directory %s already exists - cannot ' + 'overwrite!' % output_dir) + + logger.info('mkdir %s', output_dir) + os.makedirs(output_dir) + + # base jinja2 engine + env = jinja2.Environment( + loader=jinja2.PackageLoader('bob.devtools', 'templates'), + autoescape=jinja2.select_autoescape(['html', 'xml']) + ) + + # other standard files + simple = [ + 'requirements.txt', + 'buildout.cfg', + 'MANIFEST.in', + 'setup.py', + '.gitignore', + 'doc/index.rst', + 'doc/conf.py', + 'doc/links.rst', + '.gitlab-ci.yml', + 'README.rst', + 'version.txt', + ] + for k in simple: + render_template(env, k, context, output_dir) + + # handles the license file + if license == 'gplv3': + render_template(env, 'COPYING', context, output_dir) + else: + render_template(env, 'LICENSE', context, output_dir) + + # creates the base python module structure + template_dir = pkg_resources.resource_filename(__name__, os.path.join('..', + 'templates')) + logger.info('Creating base %s python module', group) + shutil.copytree(os.path.join(template_dir, 'pkg'), + os.path.join(output_dir, group)) + + # copies specific images to the right spot + copy_file(os.path.join('doc', 'img', '%s-favicon.ico' % group), output_dir) + copy_file(os.path.join('doc', 'img', '%s-128x128.png' % group), output_dir) + copy_file(os.path.join('doc', 'img', '%s-logo.png' % group), output_dir) + + # finally, render the conda recipe template-template! + # this one is special since it is already a jinja2 template + conda_env = jinja2.Environment( + loader=jinja2.PackageLoader('bob.devtools', 'templates'), + autoescape=jinja2.select_autoescape(['html', 'xml']), + block_start_string='(%', block_end_string='%)', + variable_start_string='((', variable_end_string='))', + comment_start_string='(#', comment_end_string='#)', + ) + render_template(conda_env, os.path.join('conda', 'meta.yaml'), context, + output_dir) diff --git a/bob/devtools/templates/.gitignore b/bob/devtools/templates/.gitignore new file mode 100644 index 00000000..424ce240 --- /dev/null +++ b/bob/devtools/templates/.gitignore @@ -0,0 +1,20 @@ +*~ +*.swp +*.pyc +bin +eggs +parts +.installed.cfg +.mr.developer.cfg +*.egg-info +src +develop-eggs +sphinx +dist +.nfs* +.gdb_history +build +.coverage +record.txt +miniconda.sh +miniconda/ diff --git a/bob/devtools/templates/.gitlab-ci.yml b/bob/devtools/templates/.gitlab-ci.yml new file mode 100644 index 00000000..845b719f --- /dev/null +++ b/bob/devtools/templates/.gitlab-ci.yml @@ -0,0 +1 @@ +include: 'https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/data/gitlab-ci/single-package.yaml' diff --git a/bob/devtools/templates/COPYING b/bob/devtools/templates/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/bob/devtools/templates/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>. diff --git a/bob/devtools/templates/LICENSE b/bob/devtools/templates/LICENSE new file mode 100644 index 00000000..b0de7349 --- /dev/null +++ b/bob/devtools/templates/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) {{ year }} Idiap Research Institute, http://www.idiap.ch/ +Written by {{ author }} <{{ email }}> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bob/devtools/templates/MANIFEST.in b/bob/devtools/templates/MANIFEST.in new file mode 100644 index 00000000..fac97cf2 --- /dev/null +++ b/bob/devtools/templates/MANIFEST.in @@ -0,0 +1,2 @@ +include README.rst buildout.cfg {% if license.startswith('gpl') %}COPYING {% endif %}version.txt requirements.txt +recursive-include doc *.rst *.png *.ico *.txt diff --git a/bob/devtools/templates/README.rst b/bob/devtools/templates/README.rst new file mode 100644 index 00000000..466e50a3 --- /dev/null +++ b/bob/devtools/templates/README.rst @@ -0,0 +1,46 @@ +.. -*- coding: utf-8 -*- + +.. image:: https://img.shields.io/badge/docs-stable-yellow.svg + :target: https://www.idiap.ch/software/bob/docs/{{ package }}/stable/index.html +.. image:: https://img.shields.io/badge/docs-latest-orange.svg + :target: https://www.idiap.ch/software/bob/docs/{{ package }}/master/index.html +.. image:: https://gitlab.idiap.ch/{{ package }}/badges/master/build.svg + :target: https://gitlab.idiap.ch/{{ package }}/commits/master +.. image:: https://gitlab.idiap.ch/{{ package }}/badges/master/coverage.svg + :target: https://gitlab.idiap.ch/{{ package }}/commits/master +.. image:: https://img.shields.io/badge/gitlab-project-0000c0.svg + :target: https://gitlab.idiap.ch/{{ package }} +.. image:: https://img.shields.io/pypi/v/{{ name }}.svg + :target: https://pypi.python.org/pypi/{{ name }} + + +{{ rst_title }} + +This package is part of {% if group == "bob" %}the signal-processing and machine learning toolbox Bob_{% else %}BEAT_, an open-source evaluation platform for data science algorithms and workflows{% endif %}. + +.. todo:: + + **Complete the sentence above to include one phrase about your + package! Once this is done, delete this to-do!** + + +Installation +------------ + +Complete {{ group }}'s `installation`_ instructions. Then, to install this +package, run:: + + $ conda install {{ name }} + + +Contact +------- + +For questions or reporting issues to this software package, contact our +development `mailing list`_. + + +.. Place your references here: +.. _{{ group }}: https://www.idiap.ch/software/{{ group }} +.. _installation: https://www.idiap.ch/software/{{ group }}/install +.. _mailing list: https://www.idiap.ch/software/{{ group }}/discuss diff --git a/bob/devtools/templates/buildout.cfg b/bob/devtools/templates/buildout.cfg new file mode 100644 index 00000000..de25db7d --- /dev/null +++ b/bob/devtools/templates/buildout.cfg @@ -0,0 +1,14 @@ +; -*- coding: utf-8 -*- +; {{ date }} + +[buildout] +parts = scripts +develop = . +eggs = {{ name }} +extensions = bob.buildout +newest = false +verbose = true + +[scripts] +recipe = bob.buildout:scripts +dependent-scripts = true diff --git a/bob/devtools/templates/conda/meta.yaml b/bob/devtools/templates/conda/meta.yaml new file mode 100644 index 00000000..4fd8b9e9 --- /dev/null +++ b/bob/devtools/templates/conda/meta.yaml @@ -0,0 +1,52 @@ +{% set name = '(( name ))' %} +{% set project_dir = environ.get('RECIPE_DIR') + '/..' %} + +package: + name: {{ name }} + version: {{ environ.get('BOB_PACKAGE_VERSION', '0.0.1') }} + +build: + number: {{ environ.get('BOB_BUILD_NUMBER', 0) }} + run_exports: + - {{ pin_subpackage(name) }} + script: + - cd {{ project_dir }} + {% if environ.get('BUILD_EGG') %} + - python setup.py sdist --formats=zip + {% endif %} + - python setup.py install --single-version-externally-managed --record record.txt + +requirements: + # place your build dependencies before the 'host' section + host: + - python {{ python }} + - setuptools {{ setuptools }} + # place your other host dependencies here + run: + - python + - setuptools + # place other runtime dependencies here (same as requirements.txt) + +test: + imports: + - {{ name }} + commands: + # test commands ("script" entry-points) from your package here + - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} + - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx + - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx + - conda inspect linkages -p $PREFIX {{ name }} # [not win] + - conda inspect objects -p $PREFIX {{ name }} # [osx] + requires: + - bob-devel {{ bob_devel }}.* + - nose + - coverage + - sphinx + - sphinx_rtd_theme + # extend this list with further test-time-only dependencies + +about: + home: https://www.idiap.ch/software/bob/ + license: (% if license == 'gplv3' %)GNU General Public License v3 (GPLv3)(% else %)BSD 3-Clause(% endif %) + license_family: (% if license == 'gplv3' %)GPL(% else %)BSD(% endif %) + summary: (( title )) diff --git a/bob/devtools/templates/doc/.DS_Store b/bob/devtools/templates/doc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..940e942902c4c95c53cc11d73aa9ce43019c31f2 GIT binary patch literal 6148 zcmeH~J!%6%427R!7lt%jx}3%b$PET#pTHMLVK4#Pfk0Bv(ew1vWSu%J;R&QS(yZ9s zuh>}uu>I%x1(*PA=&sm#n3*wO;SD!jzD^(a>-+t}idTWBh?%i6VYXk}5)lvq5fA|p z5P<~|$Wt7f=LJ2J9z_I1U>OAb`_SmFy>z6;r-LCz0P33MFs@^kpf)d1d+A7Jg=RH9 zShZS=AzqJmYOCvd=}66XSPdUmcQ&75XqN4;#)M`)L_q{ZU`Ak-`Q+#Sk^bBKKWkAc z0wVCw2-x~?I_&vUb+$gdp4VTi>gz$L#^nq@egc^IQM{#xaliS3+Dk_&D>VHG1O^2W H_)`MkK;;pJ literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/conf.py b/bob/devtools/templates/doc/conf.py new file mode 100644 index 00000000..62015937 --- /dev/null +++ b/bob/devtools/templates/doc/conf.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import pkg_resources + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.3' + +# 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.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.ifconfig', + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.graphviz', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.mathjax', + #'matplotlib.sphinxext.plot_directive' +] + +# Be picky about warnings +nitpicky = False + +# Ignores stuff we can't easily resolve on other project's sphinx manuals +nitpick_ignore = [] + +# Allows the user to override warnings from a separate file +if os.path.exists('nitpick-exceptions.txt'): + for line in open('nitpick-exceptions.txt'): + if line.strip() == "" or line.startswith("#"): + continue + dtype, target = line.split(None, 1) + target = target.strip() + nitpick_ignore.append((dtype, target)) + +# Always includes todos +todo_include_todos = True + +# Generates auto-summary automatically +autosummary_generate = True + +# Create numbers on figures with captions +numfig = True + +# If we are on OSX, the 'dvipng' path maybe different +dvipng_osx = '/Library/TeX/texbin/dvipng' +if os.path.exists(dvipng_osx): + pngmath_dvipng = dvipng_osx + +# 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'{{ name }}' +import time +copyright = u'%s, Idiap Research Institute' % time.strftime('%Y') + +# Grab the setup entry +distribution = pkg_resources.require(project)[0] + +# 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 = distribution.version +# The full version, including alpha/beta/rc tags. +release = distribution.version + +# 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 = ['links.rst'] + +# 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 = [] + +# Some variables which are useful for generated material +project_variable = project.replace('.', '_') +short_description = u'{{ title }}' +owner = [u'Idiap Research Institute'] + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +import sphinx_rtd_theme +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 = [sphinx_rtd_theme.get_html_theme_path()] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = project_variable + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'img/{{ group }}-logo.png' + +# 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 = 'img/{{ group }}-favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = project_variable + u'_doc' + +# -- Post configuration -------------------------------------------------------- + +# Included after all input documents +rst_epilog = """ +.. |project| replace:: Bob +.. |version| replace:: %s +.. |current-year| date:: %%Y +""" % (version, ) + +# Default processing flags for sphinx +autoclass_content = 'class' +autodoc_member_order = 'bysource' +autodoc_default_flags = [ + 'members', + 'undoc-members', + 'show-inheritance', +] + +# For inter-documentation mapping: +from bob.extension.utils import link_documentation, load_requirements +sphinx_requirements = "extra-intersphinx.txt" +if os.path.exists(sphinx_requirements): + intersphinx_mapping = link_documentation( + additional_packages=['python', 'numpy'] + + load_requirements(sphinx_requirements)) +else: + intersphinx_mapping = link_documentation() + +# We want to remove all private (i.e. _. or __.__) members +# that are not in the list of accepted functions +accepted_private_functions = ['__array__'] + + +def member_function_test(app, what, name, obj, skip, options): + # test if we have a private function + if len(name) > 1 and name[0] == '_': + # test if this private function should be allowed + if name not in accepted_private_functions: + # omit privat functions that are not in the list of accepted private functions + return skip + else: + # test if the method is documented + if not hasattr(obj, '__doc__') or not obj.__doc__: + return skip + return False + + +def setup(app): + app.connect('autodoc-skip-member', member_function_test) diff --git a/bob/devtools/templates/doc/img/beat-128x128.png b/bob/devtools/templates/doc/img/beat-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..96e1f1f6a334f22adc7119b14a89778b7b0d1609 GIT binary patch literal 25056 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 Hu0mjfb9bXL literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/img/beat-favicon.ico b/bob/devtools/templates/doc/img/beat-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..acbc0d0056cc417e7e8b71c3aac7231df78e46b7 GIT binary patch literal 27438 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 MKhUe!$HnUZ2W-4cjQ{`u literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/img/beat-logo.png b/bob/devtools/templates/doc/img/beat-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..180021b7d25cf7bee47a4c38039562462265c8ab GIT binary patch literal 13399 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 PKnNvyHMwGxY4HC5CQyi4 literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/img/bob-128x128.png b/bob/devtools/templates/doc/img/bob-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..787d77b3ec4f143f8dd66162227b5f385f5183f1 GIT binary patch literal 6547 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	I5paez= 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 FV1j@GAe{gJ literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/img/bob-favicon.ico b/bob/devtools/templates/doc/img/bob-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4cc3264302627d40868261add69eb755856611b6 GIT binary patch literal 4286 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 literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/img/bob-logo.png b/bob/devtools/templates/doc/img/bob-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b60858a7068bf45c1ed8e3da12fe244ccdcfe85d GIT binary patch literal 6266 zcmV-=7=`DFP)<h;3K|Lk000e1NJLTq006rH003kN1ONa4bn%HB00002VoOIv0RM-N z%)bBt010qNS#tmY0+;{*0+<0d(?{O`000McNliru;0Ox~6aXI@MOFX+7z;^6K~#9! z?VWj?Tve6Ff9KU+UDa!PNvAstS%44(41^`fW|R>aSyTcdvdo780Z~DQ5oDr##sPKY z1A-zThL3ziHU*JQ1OkFWSdu_UA|Wfiq?7J+*S^1d=8vlCdiCnwSFgJ|fu2vQK1o%* z<(~Jx_uO;OJ?Gxn#Mt+m$QRY26$6~IK1$mMYC@s6ed(HWZpD$+j&trqHDSp8o%>yV zN9WNweQTKdc+)=Ydjopw85bXRMPoPM!pWy@{3rdIcUv62&$dqha&P;Luin|Y*zGHB z*}CPn@8fZ`h2Mw0cIZ=1JMZxCG|ez>6Z*=P4_(i%>@zZZ2DzuH>#94uj`R3Uo{Hnp zL$_W?{rurR>=O^Y<>a#$UE4eh5CAB@t+3DMx3cj2jLdkEyMxot`+4^Xo&Ze9Y4n(F zTW-G*f0SAHeb~nbz4@fm7v0c2+pxui93Tk&t(A{RE&N_KGE`?dbW3t>5+HZ_IxoJh zYl$zYmqC|+cPuz`>qRmPzgMx-TpVDrTVMq~;n;~V?a0p_^Zn-8L;}{j4iG&4lTO{Z zh3@B%J~kt>bpG_Gb#J-6(l4gS+WSx8>?a$0oBNA*w|~*oOiJWb(E@g?`}sBemyb!? ziN3u&Fp~|a%PSzd`_&si>Ck~oF4DWQujd2+uj@FMN2TqA@JFt9H2{Qq&smO-txLW1 z`YAU#97Wt_sk;8pJD#|jKk@xLQSlnDe5(0WGx8GlMC6sPzVY<O9+UaPwE5p8hbZ9Y z3T@xD?uj4pByg&V*LcCX9iJ6y%PxAKTKT+HIeQ8F%-^~@^fKdOWE>xP{Tm#fJnrKW z^+)|k^XgD}?8BQMyvo{O?iK78T+({1D1&W8FZ7;=HoP&Jy!KDs>z}38^;C5Abym^G zy@dVP=0m;@w_f4B6hC)=@Z|L@CJ*$pmvo$>OOR8+4DI;K8&6d$V=rKz_Z!c2VS--z zKN))G3Jy&k=(9Xa?sNu3g@;~zg||L_d3BF$k7HkYM(gK<$(EO(v%cf;b$^>o24?Z? zzInRDz~sMW;_Gfm58CjE_N@DGVBImR>*lh=PtX4feemGXJTiHppSrN~1y7^Oab)g) z@(*Worg}MhIEDS`U-;$<l-~O2TR<QYdH-9J2|8)b_c<GjSFcyxRcQP3m)U{7hmFkX zi(AiBxs41KJD+;*g~?=KzWm?b1wuJ4&BQm|6dABtfqTx#w7O1SRkuLajTSneO^+SP z!;=U4@w2DD?rBx|>cGxt{&W_n?Ul6$wf*>?c<1X<4+a1-ku6tDCg?!>1FlxJuE)0h z<KOH;H({q1cqi&251)3x`9dr2LW}4L%sXCPwU(w$9_Xi>+q|&okyS?5v&j$c8cb0G za!9d0CL=Sy?tuIC1~ysX%HS1OfRz1i*e@`<2)VScxIZ23|A!l_ReCsPDb=@DGm~U{ ztJRS$hxpoLzB++Hi0xee@UD%!2a&`u29ZDx8HCjpV8H&h-!`Am2B<8-&eC7TGYZ<Q zs4O1U7nY7#QPJ^Qgr&ynhuGRj((NVPW@#pg0O+Y~GMC6kGm*^juJs#M^JZ*AQa%(M z#eU}E)@R)HT!yb47E8=U@n0oq;}N9jR9T9T$AG@tF*K&?An7?|aTH0bfxsfMJKD45 z#m(<vC5Aboz)sHdSJfSC)>>H=l-dEAs>67OwZ6;41Xk#4*Y#@EP#rlb0|I3D$Dq;g zdN$m*>A8(BU?uv?qfQ0vXa8^0cSvK2d6B`ksswqI(cDI?@Ur4_ycIw;_mkpyk|0-s zFRug!LC=JH);u)u2W&7-+#m+9WdE+eyIbVuMP3E@Uvkvu@2u!_<voNN`9zN}$O65r z>$Icm@+wG%H$3^la=cSAHpBqVeZB4|nRy{e$69GzWV&zeX6<-IW9wLXf=rBcXKIDq zc&51$%vF$0Z2$W^cj0-CkNWLrT-S8CQ0lfEwi>lveYfgZ<-qrbw#&YeE%Tz*bpqh< z&pg2qd^3tdFvNLUQ~gzN3MKC1cVW|!tZWI@EdJQEo%aH8Ks61caw|#no@6b<)p_Xb zi)agP0RU&q-MS|;q}Fx0_b%T`KCKQ)d#@)Eher#J#i!9zoQ65`#N;Q7(X^gKN(+$7 zE`9=#OHc};0&>0PCZ*ZYBb3#G<R+!wm;chdb58DGMfY$NAUF|(J`s&}R@0m}U2Sq> z!VvndU%YqYocgwf&ZDWDrqO(@wYpCe?|*k$+v8b>aeu?y;M0zVeESm>Z9rs3mc{OC z>hK<;Euao<Djh_P`S`Y0fS`>c(V!K6MK-Vg4qh%(cs}%u#(Oo7%BE+-o6iZYoHr|Q zw04lA15+Ifw1CXKlvlvehp+uRzYI7zhY5;!pZK7&O-OAz1Bie4-ZG9Lf?oUy0FIgO zx=lMC?o$6yipulo|9WfPv3(D7)F@}p6UhekRixJR-NA7TVr~BS%z5r*bP6@X!kB2_ zPXD6Bv&I>ib6vh0*rQC|apbQ(FK`YcSe4&!+#>f4+7iuAl1eI7KzczT6-!UDg? zoL2X!=2dm4+341#J<poYIpPv=H64&o7%+6H^-ga7=rT4$<!4^*zD;dF^x=UevDN0S zbN_{Ir+L;kKDM^+>%6A)d-gAV-yk@kL4yPNZ;sJ3Qrq7>Dp3(zKj|XvhgvsPbiQQJ z`Tmuwmh$!q$}V*{Tt6YNG20@H85oYd)GJA7ymfEli0C^Bq>)8#RO7kq?d<S;#t_4A zaQp{c#{FfvD?R>5d`+dC51+#c;-3ujyVSZEo-T)BS!35cZD|f!q>w-i5ri>}-Pny` zgb_nDIq=hjB%$%ra{gkjPanN+z3l4pzXQ{8_=F|(X8qW8K&22J86*+IVDEZGHhn`3 zUlLnciacM6R@ZPWjjCncue2HEQjBM0HV<4cpIf$Z2Vbo35|U~yN^&#>4cJ}xQFHlz zhCmi+BoM{Ws%^iKb9iwLU!9AYksH5zTbdV65cUP#t_yT37M$0SFh&a65ls*NEYq(# z_k->DC9@29@w?W9!pfk|Bk5Ebw)|U<MgCKrnceg{{#dEwMv3m}#p<1Ykz<ir4NBj$ zyM`{~q)N_;NBFPCK1w%L0NkwyOc3_q{o0fqQiv5`hY&#m>4M=%uHJR8)!E`UeVtjy zjX<al?EpaJ{3Qj|rXsZ>{Iq54<_58|@;rl_LiM)z9XG;lhF$zFa*2mx?_0?U|5qfE zK*j{Tr1PFO(k1}=f-k2Rk6;8L#E?L`P|KqYe`x43PFS74DTg<$%nbR^4FH|jX@{7t zdFhO7@6Z+2S^F)+{Ju=fi`yuG4gm1qNwehbPCy{@;lMI$9&6r@ZH{0V!w4aUByuJn zjRL5zIhWe(Y55=Bqly{|J^4`Ly{ggjKC7`PdC>$+Y4LrN9V)*|q!YJid#cLWUMia^ zM#SVpD>io=>^X}^Q+GfqLk0kPBy?@{Ic)q99|mNRMH+wuPB`I&tKbQdAAy>&H{R-) zE0iJI%!dR2Reb`rSY;LFVh3`7`#037E-1Tt<SrY@ke*5z2K)|W0O!5bP#T(1Ie1RL z6nWkTl#h&8nnNxhmnooULb^um)8_fkVP%9o7q#l-3<y?oJinMs23BOSvwewc2`gie zVt4rRVr<V+&T9t6m9!Z2O})~!P)PNh0pxmzuC%k_gtr#YGm6V0hctk60+AZAd+yVk zSsC6+uLwVDr<CnGx<^o_3WkX^1|7@cR|drN)T602cI1e+5v6)r<q-gp@G)J>&dU7- zgsH@B**-gR_*DqCvyzE(ZQ4HLeAgniu4k%tat0vKl&KDpY`uaGWMytCw`t@Cn=3(b z>QV?IR)3o|olWMYly1+g8M)iu-Biw(F>1XYu5o&jc5U9#k_EFwCVp?W*IqeYPWV|l zMS~O0b68WQIXE7_$?h~KZfBn=M8Mz-eL<B_A?U-QZ`m<q0KjvgsjXSIw<hd!rqLXA zBt5$$bdTMVxV4Bwrtw+>rmB@W*OPeCww!LbSb-IAbWmf-<d~1kZcMMTEoZjHK0Vb_ z6ZYJ<H6JSy7mVyW9<Y6xGvQ%n3WMsQbR5B%mpNuz&H_~^i~#{3OH4v>4nX2z`zypn zw>oVu(wTKNVdoQBnWPZ$$UXK;5@(X8av(*_YKE8?iH$|hF`Cj*6(bZhbf4zP=FY@p zV~}&OCbjASkR8shtO5J1Mw-XUG+nkg^U4@1aG}M%gw~=qG5R)MKL$Amvogm7qK1lr zTFG_XfIhoYrW4QOiVk@c`zG|&fStdNpxnFwK<4eStViN`q!=mzAUISWJ?p7w?a#7p zYd|+kObi2n;5au+Oi%tZ8Gpe3s#$R`+Sr79bY5I0_Iu_ULSZ}_w?J8p17PU!$g}oK zYW+NQ7|(~HL_IDx%eEI{B%$OA`*QS4(4Z<y4~gBf?ZxCt=B+WvxuTR2Fqf7cia!I? zpzWfaph`Y-O6(j%QQys9h0Mp$cWSJj9VSxf8H1vn%N0YQQTL$F=~EdPK*X`b_PCu; zF`h1Ier{9VSdClTMMt@Y8+Aob*|&skK^)FyGZl=1R`Dh`lxtoY$T8q(UO@9r<AKs! z*PlYLT*u4qE{dpa|L$y>QQ0o@<%mq;*Lf`(f8e*p*oevniG+O|`VXl|p0)r)fBbvJ zj!yO#5!iPge+X}-gks06-^&5{H2@3|8pCqs%i*Y;Ia&x#uZumA-#-2>mCTNqK_8=t z*efOA^L$02$>3b(f!wxcP1)IkN9=ZV!7k^CT;abIt~;5&#g|}*x?9g}w@YQpG0uf7 zl|m4a=(WW;5aajT!*b*dKu_4!4%e;JrJCy~HpXuO%!480G5Vn0i`DKy6HBZ%0OVFD zAM~Y><T{m?le29ywOdhV>_3bnfy|1;s8c<D>?7*w|KufawP!j5K(O8YWi!fBtR;OD zhXDDLHws5J+0^wTwc}JnAu*i#HYb3lIHsT?G(Z5LX)QK&eW~LxV@$JbIMyG!sx;0W zzikgy&VC<760irvaSx`Ldxp`t;v1<a0DwlIu(<%WkPWpVC*4tu=2w)%`qSBW0DvQA z2eR~Cn`!HAeE30ex|Nv?XVc5GhUbNi-*4Y3N6rAqi>~S=)L*DAfx>$b+tZik2SU6< z?z3~gdRwc@sar;esVYw2khmsq5UCfC`!aymLN-<~Hs0;nUq%A}a&LuxTxN{Mt?iE@ zs}e$9%&B&|f426cV(P+tvtnoFYnjc3QeTx<41<9>98^`#V!DEzGXI1jwx!Mzh5mt8 z%aH>N)LTB$FAx2E?X04kVWy(54}Yb6LgDzaza_TIkTV$6>^_!!mJL7PdRXg(^hS9k zb5-)S(L)~*yHy@FwP;6?<sCR%wdH7%kAcNN`n=2sqj&IoPO-wsINE7ZmCBvtxP|KE zW>;*Bo^GBeG;ZuWnBJ8k2LN(=Pr$(z&>MpHX@`~HTCoHX&)$?WMEk-KS`VqW%|O8Q zuh`E*PK*0d%#{ZO#BlcV^gj%8-seq_R1N@8ljjVwFlWwA?_*jgOBWl7fyB~e#Uu1< znJ2RxX;dK3e>$hqiPWZm(Hyv6I}@68sFljymHb7y`^kUId*?FiOf&FVQgq*1yZ3QA zR&Jg+np>8B!gRv(az$V;x#nXD1?6(wGlS3MXYiOQ0Q&CarLi|93~HJ2+x|R0hl(g< zA@sW`atoehlV?qSY7X4*IKh~>Fx+55Jdn8BJeet=;cXnI7%~a{{q)Uv&K&QgAhmiQ zp-;;)YXlC9pQUaw&jAPqu$tzWqmYGfxy+l%pWsFFD1sVXGwL33>|cKLsMkM~y)L|5 zHl(W!`(l2Uf~tiunN87~@f=b}aRMMeT=09&@LaAPRO)(+=y_25DDf*<Ah+&f+yjSv z$ONIU&D@MvkVcYI0QuoYz;%J+LYgCwt#hBaD*dD^R}}mKmZ?@RaW?x#@~3zcX(TyK z1V7xU_h0E=s&&g3m5D8x3!|^e$I&&;Z*RK~7ZqmiNI>ScWu8vI&AYG#UQBf#=s8cD zUwoKE#S)rL*$Y#DS2}^u;bhgyCg@_5{-pjc2C)r6bT|)poK15|9<(W@xHk8d><5Za zg@;z)=t|^;Hj>rXW&WI9k6q|N6FPj0U1vJ>gG0KsOxIV&&q*ueqqSn!_!&CcV#TT8 zs2CO@tpN=bG!7=pK6U-|^tm~Uc?Nzz{)UC>MP(2i=G}-<BN`|Gmm-6d;lE@q7DE;> zqIO=P9VpZG!e>r|d5BWfNDUMuhx|B>i-$s2vqfC0=G7c+el`K&F@A~=Ka{wRs|3${ zU09JH4X9^_NpG-p+%EoA_Q%%KV1dA)cnZ_h%Ourn_D0Q7Z|<hZZ>_Ul6R?P;wTU0b zNk!W`Hs~VGEA^|>>+EFZz5C(!SR_ZymcRjoc!juD-(n|X88`y>;oxe(<=}acpGjPw z?ynlKn3%OoEjQC;XsopHmg`SbVvYX2)a&-Mp<XBM;ibhz2DYy}hBw4_^>y|qTm=01 z9ZrLDY~T?RucW`5+H9w4Cv3@B%V%&s79%M0Y9xz;N3c=<S@P*>O}_Hw`4X<=BjK}! zo-u^=yi9+?e$|e4;+N7z_;hh`S{3kuck#Q~?<LmSt=b8FL|fyhv>R{~brz>@O5h6~ z;0^pz_IH_qv7;D`(jQOP@IrJz{q&_A`mr9*h{t$n{7;3iJBu#EA=C;|YIp{{yq5oz zd@480V_4lg;ZMf%J6(&l)3HCMaR5Ge;o=nHh$74*7#1(4p4EpZ;)%WvADx6#Fc<9z zz|S5ykVFCzgfN0({8_w=@I*YC+|%IvJT0L)+(rTT;2{kO#1W+shB2r=8h?u;V=r}& zJ&=$HZa7_jM<5dtKpM$C^L%#F;D(dx;NuYxGGjMeHsv_rg3}!!PdWrb%1#<*uVW`C z8qkC$c;JF7KaCLS!aI(^+Ai84$Au;|p#dJa;LJZhpdclKC<g6L^sXw$gC;bg9&We_ zqk?QfN*ZAdjrA|+&>nbP$P`RRW0i*%MKOpWj@Hzg+R=^rDqU(ALm09<L0X~ZQ_+RG zDso0Jh#~vRG@;lD=s-98wogc72YOi_2l8p?g2(m=N%UgJ*o_=H(2Y*GY@ZNA4|eh1 z#ZE5FwVPBnyt3%Qz*vxw7yBbHW;rQ*gu$`gE!E+GaXgMRj_uXgYfTt-3PKWovW=t{ zyX{>GHDf<G#wjO(9=lUwsU6eDosEc~$9mTN1YxHD=GuDbq7{-Y)jL3Hw8|#gM6#`V z2T1Lx^+CA9_(<(2PY8B$V~)j)8yyM6kO0984}!`zp<{a$9r0tfMdjuYM;r+_;DrbE z$}7rXyZV^{06-0xVQCiQh$9Inyzrnw*(Orhrc$H{z)l+WvoJ3)^cXES5gO5|M6y%; zH(bbNX<kOKgUhd~87+$A8U5-%I7D8|vZ&l$JY?#2r=UrJUPrIoi<$`REoiYY9etJE zB@w15JKrdF${QzjSh$N346w`-X;*f>BN$R-e!8Ka^)Sps(osr>(tjJ0ts|@%J2}v; zY-VZnt9%q{M6=Qe?5K1q$!#&^DUO}29!1nE-AaiEMLtYb$_cC5A`#k@zJ09HsjMA) z6M_~EGG*BftHJLy1EDDv<AHvwPLdiez*o4G)S)z(j6RD_5@=C^kD6V|8W+D4;8FgK z(;O^EZ5$&gfM{r<VKzrzRC3bRo(YGD6+m2QK-gq1G^HHNdCUyj4OaO1KFXFm3$;FB z!wCV^#l)(%q!^qE-EtyWwt~WC@ds?HFD;0`p}12I)s=O6ZVNe8LJSNFp;=*Y{ggAd z{MxbSEQbE5dZA&Z1Lh*1!OgRhQ`OsNrZDOi-ie2DhRDA|DTk}a?O9MrY0&(nmrAG= zyJmsIMpT`dVoICsqTJ|(k_8SM-4p8+Q~D<!((`7D@}^NWs<83m9L1HfA}{GhGgT{g zQ5z0`bCgnEg_ClHQq-o*X3B)K<WPECF3LZ9gDl|M4cMijDZPGI(E`+pT~}U?eHSTf zajXU%ARt>yn;He;w2&hT*HWV#2Bp=5I?@WW>1%Ny$?{JD*pDg_S=ymVCdayzd@(c$ k{u1*4!<V)X!N>9XKYrHF6~+H2b^rhX07*qoM6N<$g7`X0ivR!s literal 0 HcmV?d00001 diff --git a/bob/devtools/templates/doc/index.rst b/bob/devtools/templates/doc/index.rst new file mode 100644 index 00000000..39014e71 --- /dev/null +++ b/bob/devtools/templates/doc/index.rst @@ -0,0 +1,23 @@ +.. -*- coding: utf-8 -*- + +.. _{{ package }}: + +{{ rst_title }} + +.. todo :: + Write here a small (1 paragraph) introduction explaining this project. See + other projects for examples. + + + +Users Guide +=========== + +.. toctree:: + :maxdepth: 2 + + api + +.. todolist:: + +.. include:: links.rst diff --git a/bob/devtools/templates/doc/links.rst b/bob/devtools/templates/doc/links.rst new file mode 100644 index 00000000..e3da9b03 --- /dev/null +++ b/bob/devtools/templates/doc/links.rst @@ -0,0 +1,8 @@ +.. -*- coding: utf-8 -*- + +.. This file contains all links we use for documentation in a centralized place + +.. _idiap: http://www.idiap.ch +.. _bob: http://www.idiap.ch/software/bob +.. _installation: https://www.idiap.ch/software/bob/install +.. _mailing list: https://www.idiap.ch/software/bob/discuss diff --git a/bob/devtools/templates/pkg/__init__.py b/bob/devtools/templates/pkg/__init__.py new file mode 100644 index 00000000..2ab1e28b --- /dev/null +++ b/bob/devtools/templates/pkg/__init__.py @@ -0,0 +1,3 @@ +# see https://docs.python.org/3/library/pkgutil.html +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/bob/devtools/templates/requirements.txt b/bob/devtools/templates/requirements.txt new file mode 100644 index 00000000..a85739ed --- /dev/null +++ b/bob/devtools/templates/requirements.txt @@ -0,0 +1,2 @@ +setuptools +numpy diff --git a/bob/devtools/templates/setup.py b/bob/devtools/templates/setup.py new file mode 100644 index 00000000..ba9f4583 --- /dev/null +++ b/bob/devtools/templates/setup.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +{% if group == 'beat' %}from setuptools import setup, find_packages + +def load_requirements(f): + retval = [str(k.strip()) for k in open(f, 'rt')] + return [k for k in retval if k and k[0] not in ('#', '-')] + +install_requires=load_requirements('requirements.txt') +{% else %}from setuptools import setup, dist +dist.Distribution(dict(setup_requires=['bob.extension'])) + +from bob.extension.utils import load_requirements, find_packages +install_requires = load_requirements() +{% endif %} + +setup( + + name='{{ name }}', + version=open("version.txt").read().rstrip(), + description='{{ title }}', + + url='https://gitlab.idiap.ch/{{ package }}', + {% if license == 'gplv3' %}license='GPLv3'{% else %}license='BSD'{% endif %}, + + # there may be multiple authors (separate entries by comma) + author='{{ author }}', + author_email='{{ email }}', + + # there may be a maintainer apart from the author - you decide + #maintainer='?' + #maintainer_email='email@example.com' + + # you may add more keywords separating those by commas (a, b, c, ...) + keywords = "{{ group }}", + + long_description=open('README.rst').read(), + + # leave this here, it is pretty standard + packages=find_packages(), + include_package_data=True, + zip_safe = False, + + install_requires=install_requires, + + entry_points={ + # add entry points (scripts, {{ group }} resources here, if any) + }, + + # check classifiers, add and remove as you see fit + # full list here: https://pypi.org/classifiers/ + # don't remove the Bob framework unless it's not a {{ group }} package + classifiers = [ + {% if group == 'bob' %}'Framework :: Bob', + {% endif %}'Development Status :: 4 - Beta', + 'Intended Audience :: Science/Research', + {% if license == 'gplv3' %}'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'{% else %}'License :: OSI Approved :: BSD License'{% endif %}, + 'Natural Language :: English', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + +) diff --git a/bob/devtools/templates/version.txt b/bob/devtools/templates/version.txt new file mode 100644 index 00000000..30893e11 --- /dev/null +++ b/bob/devtools/templates/version.txt @@ -0,0 +1 @@ +0.0.1b0 diff --git a/conda/meta.yaml b/conda/meta.yaml index 29370595..23f5ed3a 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -45,6 +45,7 @@ requirements: - pyyaml - twine - lxml + - jinja2 test: requires: @@ -64,6 +65,7 @@ test: - bdt build --help - bdt getpath --help - bdt caupdate --help + - bdt new --help - bdt ci --help - bdt ci build --help - bdt ci deploy --help diff --git a/doc/index.rst b/doc/index.rst index f26dfe22..c5c9d035 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,6 +17,7 @@ Documentation install release + templates api ci diff --git a/doc/templates.rst b/doc/templates.rst new file mode 100644 index 00000000..17d864a3 --- /dev/null +++ b/doc/templates.rst @@ -0,0 +1,523 @@ +.. vim: set fileencoding=utf-8 : + +.. _bob.devtools.templates: + +========================== + New Package Instructions +========================== + +These instructions describe how create new packages for either Bob_ or BEAT_ +and provides information on how to generate a complete, but empty package from +scratch. + +.. note:: + + If you'd like to update part of your package setup, follow similar + instructions and then **copy** the relevant files to your **existing** + setup, overriding portions you know are correct. + + +.. warning:: + + These instructions may change as we get more experience in what needs to be + changed. In case that happens, update your package by generating a new + setup and copying the relevant parts to your existing package(s). + + +Create a new package +-------------------- + +To create a new package, just use the command ``bdt new``. Use its ``--help`` +to get more information about options you can provide. + + +Continuous Integration and Deployment (CI) +------------------------------------------ + +If you'd like just to update CI instructions, copy the file ``.gitlab-ci.yml`` +from ``bob/devtools/templates/.gitlab-ci.yml`` **overriding** your existing +one: + + +.. code-block:: sh + + $ curl -k --silent https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/templates/.gitlab-ci.yml > .gitlab-ci.yml + $ git add .gitlab-ci.yml + $ git commit -m '[ci] Updated CI instructions' .gitlab-ci.yml + + +The ci file should work out of the box, it is just a reference to a global +configuration file that is adequate for all packages inside the Bob_/BEAT_ +ecosystem. + +You also remember to enable the following options on your project: + +1. In the project "Settings" page, make sure builds are enabled +2. Visit the "Runners" section of your package settings and enable all runners + with the `docker` and `macosx` tags. +3. Setup the coverage regular expression under "CI/CD pipelines" to have the + value `^TOTAL.*\s+(\d+\%)$`, which is adequate for figuring out the output + of `coverage report` + + +New unexisting dependencies +--------------------------- + +If your package depends on **third-party packages** (not Bob_ or BEAT_ existing +resources) that are not in the CI, but exist on the conda ``defaults`` channel, +you should perform some extra steps: + +1. Add the package in the ``meta.yml`` file of bob-devel in + ``bob/bob.conda/conda/bob-devel``: + + + .. code-block:: yaml + + requirements: + host: + - python {{ python }} + - {{ compiler('c') }} + - {{ compiler('cxx') }} + # Dependency list of bob packages. Everything is pinned to allow for better + # reproducibility. Please keep this list sorted. It is recommended that you + # update all dependencies at once (to their latest version) each time you + # modify the dependencies here. Use ``conda search`` to find the latest + # version of packages. + - boost 1.65.1 + - caffe 1.0 # [linux] + - click 6.7 + - click-plugins 1.0.3 + - .. + - [your dependency here] + +2. At the same file, update the version with the current date, in the format + preset. + + .. code-block:: yaml + + package: + name: bob-devel + version: 2018.05.02 <-- HERE + +3. Update the ``beat-devel`` and ``bob-devel`` versions in the ``meta.yml`` + file inside ``bob/bob.conda/conda/beat-devel``: + + .. code-block:: yaml + + package: + name: beat-devel + version: 2018.05.02 <-- HERE + + [...] + + requirements: + host: + - python {{ python }} + - bob-devel 2018.05.02 <-- HERE + - requests 2.18.4 + +4. Update the ``conda_build_config.yaml`` in + ``bob/bob.devtools/bob/devtools/data/conda_build_config.yaml`` with your + dependencies, and with the updated version of bob-devel and beat-devel. See + `this here <https://gitlab.idiap.ch/bob/bob.conda/merge_requests/363>`_ and + `this MR here <https://gitlab.idiap.ch/bob/bob.admin/merge_requests/89>`_ + for concrete examples on how to do this. + + .. note:: + + **This step should be performed after bob.conda's pipeline on master is + finished** (i.e. perform steps 1 to 3 in a branch, open a merge request + and wait for it to be merged, and wait for the new master branch to be + "green"). + + +Conda recipe +------------ + +The CI system is based on conda recipes to build the package. The recipes are +located in the ``conda/meta.yaml`` file of each package. You can start +to modify the recipe of each package from the template generated by ``bdt +template`` command as explained above, for new packages. + +The template ``meta.yaml`` file in this package is up-to-date. If you see a +Bob_ or BEAT_ package that does not look similar to this recipe, please let us +know as soon as possible. + +You should refrain from modifying the recipe except for the places that you are +asked to modify. We want to keep recipes as similar as possible so that +updating all of them in future would be possible by a script. + +Each recipe is unique to the package and need to be further modified by the +package maintainer to work. The reference definition of the ``meta.yaml`` file +is https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html. +The ``meta.yaml`` file (referred to as the recipe) will contain duplicate +information that is already documented in ``setup.py``, ``requirements.txt``, +and, eventually, in ``test-requirements.txt``. For the time being you have to +maintain both the ``meta.yaml`` file and the other files. + +Let's walk through the ``conda/meta.yaml`` file (the recipe) that you just +created and further customize it to your package. You need to carry out all +the steps below otherwise the template ``meta.yaml`` is not usable as it is. + + +Entry-points in the ``build`` section +===================================== + +You need to check if your package has any ``console_scripts``. These are +documented in ``setup.py`` of each package. You need to list the +``console_scripts`` entry points (only ``console_scripts``; other entry points +**should not** be listed in ``conda/meta.yaml``) in the build section of the +recipe. + +* If there are no ``console_scripts``, then you don't need to add anything +* If there are some, list them in the ``conda/meta.yaml`` file as well: + (`information on entry-points at conda recipes here + <https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#python-entry-points>`_). + For example, if the ``setup.py`` file contains: + + .. code-block:: python + + entry_points={ + 'console_scripts': [ + 'jman = gridtk.script.jman:main', + 'jgen = gridtk.script.jgen:main', + ] + + You would add the following entry-points on ``conda/meta.yaml``: + + .. code-block:: yaml + + build: # add entry points at the "build" section + entry_points: + - jman = gridtk.script.jman:main + - jgen = gridtk.script.jgen:main + + +.. note:: + + If your conda package runs only on linux, please add this recipe under + build: + + .. code-block:: yaml + + build: + skip: true # [not linux] + + +Build and host dependencies +=========================== + +This part of the recipe lists the packages that are required during build time +(`information on conda package requirements here +<https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#requirements-section>`_). +Having build and host requirements separately enables cross-compiling of the +recipes. Here are some notes: + +* If the packages does not contain C/C++ code, you may skip adding build + dependencies (pure-python packages do not typically have build dependencies + (that is, dependencies required for installing the package itself, except for + ``setuptools`` and ``python`` itself) +* If the package does contain C/C++ code, then you need to augment the entries + in the section ``requirements / build`` to include: + + .. code-block:: yaml + + requirements: + build: + - {{ compiler('c') }} + - {{ compiler('cxx') }} + - pkg-config {{ pkg_config }} + - cmake {{ cmake }} + + The pkg-config and cmake lines are optional. If the package uses them, you + need to include these as well. + +* List all the packages that are in ``requirements.txt`` in the + ``requirements / host`` section, adding a new line per dependence. For + example, here is what ``bob/bob.measure`` has in its host: + + .. code-block:: yaml + + host: + - python {{ python }} + - setuptools {{ setuptools }} + - bob.extension + - bob.blitz + - bob.core + - bob.math + - bob.io.base + - matplotlib {{ matplotlib }} + - libblitz {{ libblitz }} + - boost {{ boost }} + - numpy {{ numpy }} + - docopt {{ docopt }} + + You need to add a jinja variable like `{{ dependence }}` in front of the + dependencies that we **do not** develop. The jinja variable name should not + contain ``.`` or ``-``; replace those with ``_``. Bob_ and BEAT_ packages + (and gridtk) should be listed as is. + +* Unlike ``pip``, ``conda`` is **not** limited to Python programs. If the + package depends on some non-python package (like ``boost``), you need to list + it in the `host` section. + + +Runtime dependencies +==================== + +In the ``requirements / run`` section of the conda recipe, you will list +dependencies that are needed when a package is used (run-time) dependencies. +Usually, for pure-python packages, you list the same packages as in the host +section also in the run section. This is simple, **but** conda build version +3.x introduced a new concept named ``run_exports`` (`read more about this +feature here +<https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#pin-downstream>`_) +which makes this slightly complicated. In summary, you put all the run-time +dependencies in the ``requirements / run`` section **unless** this dependency +was listed in the host section **and** the dependency has a ``run_exports`` set +on their own recipe (what a mess!). The problem is that you cannot easily find +which packages actually do have ``run_exports`` unless you look at their conda +recipe. Usually, all the C/C++ libraries like ``jpeg``, ``hdf5`` have +``run_exports`` (with exceptions - ``boost``, for instance, does not have +one!). All ``bob`` packages have this too. For example, here is what is +inside the ``requirements / run`` section of ``bob/bob.measure``: + +.. code-block:: yaml + + run: + - setuptools + - matplotlib + - boost + - {{ pin_compatible('numpy') }} + - docopt + +The ``pin_compatible`` jinja function is `explained in here +<https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#pin-downstream>`_. +You need to use it on ``numpy`` if and only if you use ``numpy`` in C level. +Otherwise, just list numpy normally. We do not know of any other package +besides numpy used in C level that needs to use the ``pin_compatible`` jinja +function. + +Here is a list of packages that we know that they have ``run_exports``: + +.. code-block:: yaml + + - bzip2 + - dbus + - expat + - ffmpeg + - fontconfig + - freetype + - giflib + - glib + - gmp + - gst-plugins-base + - gstreamer + - hdf5 + - icu + - jpeg + - kaldi + - libblitz + - libboost + - libffi + - libmatio + - libogg + - libopus + - libpng + - libsvm + - libtiff + - libvpx + - libxcb + - libxml2 + - menpo + - mkl # not this one but mkl-devel - no need to list mkl if you use mkl-devel in host + - mkl-devel + - ncurses + - openfst + - openssl + - readline + - sox + - speex + - speexdsp + - sqlite + - tk + - vlfeat + - xz + - yaml + - zlib + + +Testing entry-points +==================== + +If you listed some of your ``setup.py`` ``console_sripts`` in the ``build / entry_points`` section of the conda recipe, it is adviseable you test these. For +example, if you had the examples entry points above, you would test them like: + +.. code-block:: yaml + + test: + imports: + - {{ name }} + commands: + - jman --help + - jgen --help + + +Test-time dependencies +====================== + +You need to list the packages here that are required during **test-time only**. +By default, add some packages. Do not remove them. The test-time dependencies +are listed in ``test-requirements.txt``, which is an optional file, not +included in the template. It has the same syntax as ``requirements.txt``, but +list only things that are needed to test the package and are not part of its +runtime. If you do not need any test-time dependencies, you may skip these +instructions. + +You may read more information about `conda test-time dependencies here <https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#test-requirements>`_. + + +Left-over conda build files +--------------------------- + +The conda build command may create a temporary file named ``record.txt`` in the +project directory. Please make sure it is added in the ``.gitignore`` file so +that is not committed to the project repository by mistake. + + +Database packages and packages with extra data +---------------------------------------------- + +Sometimes databases or other packages require an extra download command after +installation. If this extra data is downloaded from Idiap severs, you can +include this data in the conda package itself to avoid downloading it two +times. If the data is supposed to be downloaded from somewhere other than Idiap +servers, do not include it in its conda package. For example, the database +packages typically require this download command to be added in the +``build:script`` section: + + +.. code-block:: yaml + + - python setup.py install --single-version-externally-managed --record record.txt # this line is already in the recipe. Do not add. + - bob_dbmanage.py {{ name.replace('bob.db.', '') }} download --missing + + +Licensing +--------- + +There are 2 possible cases for the majority of packages in our ecosystem: + +1. If the package is supposed to be licensed under (a 3-clause) BSD license, + ensure a file called ``LICENSE`` exists at the root of your package and has + the correct authorship information. +2. If the package is supposed to be licensed under GPLv3 license, then ensure a + file called ``COPYING`` exists on the root of your package + +The templating generation has an option to address this. + +More info about Idiap's `open-source policy here +<https://secure.idiap.ch/intranet/services/technology-transfer/idiap-open-source-policy>`. + + +Headers +------- + +Sometimes people add headers with licensing terms to their files. You should +inspect your library to make sure you don't have those. The Idiap TTO says this +strategy is OK and simplifies our lives. Make the headers of each file you have +as simple as possible, so they don't get outdated in case things change. + +Here is a minimal example (adapt to the language comment style if needed): + +```text +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +``` + +It is OK to also have your author name on the file if you wish to do so. +**Don't repeat licensing terms** already explained on the root of your package +and on the `setup.py` file. If we need to change the license, it is painful to +go through all the headers. + + +The ``setup.py`` file +--------------------- + +The ``setup.py`` should be changed to include eventual ``entry_points`` you +also included in the ``conda/meta.yaml``. We cannot guess these. + + +Buildout +-------- + +The default buildout file ``buildout.cfg`` should buildout from the installed +distribution (use ``bdt create`` for that purpose) and **avoid mr.developer +checkouts**. If you have one of those, move it to ``develop.cfg`` and create a +new `buildout.cfg` which should be as simple as possible. The template project +provided by this package takes care of this. + + +The ``README.rst`` file +----------------------- + +You should make the README smaller and easier to maintain. As of today, many +packages contain outdated installation instructions or outdated links. More +information can always be found at the documentation, which is automatically +linked from the badges. + +You may want to revise the short introduction after automatic template +generation. Make it short, a single phrase is the most common size. + + +Sphinx documentation +-------------------- + +Sphinx documentation configuration goes to a file named ``doc/conf.py``. The +file ``doc/index.rst`` is the root of the documentation for your package. + +The new documentation configuration allows for two *optional* configuration +text files to be placed inside the ``doc/`` directory, alongside the ``conf.py`` file: + +* ``extra-intersphinx.txt``, which lists extra packages that should be + cross-linked to the documentation (as with `Sphinx's intersphinx extension + <http://www.sphinx-doc.org/en/stable/ext/intersphinx.html>`_. The format of + this text file is simple: it contains the PyPI names of packages to + cross-reference. One per line. +* ``nitpick-exceptions.txt``, which lists which documentation objects to ignore + (for warnings and errors). The format of this text file is two-column. On the + first column, you should refer to `Sphinx the object + type <http://www.sphinx-doc.org/en/stable/domains.html#the-python-domain>`_, + e.g. ``py:class``, followed by a space and then the name of the that should be + ignored. E.g.: ``bob.bio.base.Database``. The file may optionally contain + empty lines. Lines starting with ``#`` are ignored (so you can comment on why + you're ignoring these objects). Ignoring errors should be used only as a + **last resource**. You should first try to fix the errors as best as you can, + so your documentation links are properly working. + + +.. tip:: + + You may use ``bdt dumpsphinx`` to list *documented* objects in remote sphinx + documentations. This resource can be helpful to fix issues during sphinx + documentation building. + + +Project logo and branding +------------------------- + +In the gitlab Settings / General page of your project, update the logo to use +one of ours: + +* For Bob_: + + .. image:: https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/templates/doc/img/bob-128x128.png + :alt: Bob's logo for gitlab + +* Fob BEAT_: + + .. image:: https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/templates/doc/img/beat-128x128.png + :alt: BEAT's logo for gitlab + + +.. include:: links.rst diff --git a/setup.py b/setup.py index 518246ba..178fd465 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ requires = [ 'pyyaml', 'twine', 'lxml', + 'jinja2', ] setup( @@ -43,6 +44,7 @@ setup( ], 'bdt.cli': [ 'release = bob.devtools.scripts.release:release', + 'new = bob.devtools.scripts.new:new', 'changelog = bob.devtools.scripts.changelog:changelog', 'lasttag = bob.devtools.scripts.lasttag:lasttag', 'visibility = bob.devtools.scripts.visibility:visibility', -- GitLab