diff --git a/tools/cookiecutter/README.md b/tools/cookiecutter/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a1584e0131f354da14d3c86e35f30ee8416ea624
--- /dev/null
+++ b/tools/cookiecutter/README.md
@@ -0,0 +1,44 @@
+Woob module cookiecutter template
+=================================
+
+This is a basic template for scaffolding woob modules with cookiecutter.
+
+How to use
+----------
+
+Run cookiecutter:
+
+ cookiecutter -o ../../modules .
+
+It will ask a few questions:
+
+ full_name [Your Name]:
+ email [yourmail@example.com]:
+ site_url [https://example.com]:
+ site_name [Example]:
+ capability [CapSomething]:
+ module_name [twitter]:
+ class_prefix [TwitterSimple]:
+ year [2021]:
+
+Develop by editing files in `../modules/your_module/*.py`.
+
+Don't forget to let woob detect your module:
+
+ woob update
+
+Create an instance of your module:
+
+ [my_backend]
+ _module = my_module
+ login = something
+ password =
+
+Then you can test your module with the appropriate command:
+
+ woob something -d -b my_backend
+
+Requirements
+------------
+
+This template requires at least cookiecutter 1.7.1.
diff --git a/tools/cookiecutter/cookiecutter.json b/tools/cookiecutter/cookiecutter.json
new file mode 100644
index 0000000000000000000000000000000000000000..b3993795d117e64a13ab5d4c08f36839ab24bb12
--- /dev/null
+++ b/tools/cookiecutter/cookiecutter.json
@@ -0,0 +1,14 @@
+{
+ "full_name": "Your Name",
+ "email": "yourmail@example.com",
+ "site_url": "https://example.com",
+ "site_name": "Example",
+ "capability": "CapSomething",
+ "module_name": "{{cookiecutter.site_name | slugify | replace('-', '_')}}",
+ "class_prefix": "{{cookiecutter.module_name | replace('_', ' ') | title | replace(' ', '')}}",
+ "year": "{% now 'utc', '%Y' %}",
+ "_extensions": [
+ "jinja2_time.TimeExtension",
+ "cookiecutter.extensions.SlugifyExtension"
+ ]
+}
diff --git a/tools/cookiecutter/{{cookiecutter.module_name}}/__init__.py b/tools/cookiecutter/{{cookiecutter.module_name}}/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..53fe825b9af3c761e7c7e20686f34de36f6b494a
--- /dev/null
+++ b/tools/cookiecutter/{{cookiecutter.module_name}}/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) {{cookiecutter.year}} {{cookiecutter.full_name}}
+#
+# This file is part of a woob module.
+#
+# This woob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This woob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this woob module. If not, see .
+
+from __future__ import unicode_literals
+
+from .module import {{cookiecutter.class_prefix}}Module
+
+
+__all__ = ["{{cookiecutter.class_prefix}}Module"]
diff --git a/tools/cookiecutter/{{cookiecutter.module_name}}/browser.py b/tools/cookiecutter/{{cookiecutter.module_name}}/browser.py
new file mode 100644
index 0000000000000000000000000000000000000000..19e37bf6913429d695a869c24e2863a811bacf7e
--- /dev/null
+++ b/tools/cookiecutter/{{cookiecutter.module_name}}/browser.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) {{cookiecutter.year}} {{cookiecutter.full_name}}
+#
+# This file is part of a woob module.
+#
+# This woob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This woob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this woob module. If not, see .
+
+# flake8: compatible
+
+from __future__ import unicode_literals
+
+from woob.browser import LoginBrowser, URL, need_login
+from woob.exceptions import BrowserIncorrectPassword
+
+from .pages import (
+ LoginPage, SomethingPage,
+)
+
+
+class {{cookiecutter.class_prefix}}Browser(LoginBrowser):
+ BASEURL = "{{cookiecutter.site_url}}"
+
+ login = URL(r"/login", LoginPage)
+ something = URL(r"/something", SomethingPage)
+
+ def do_login(self):
+ self.login.go()
+ self.page.do_login(self.username, self.password)
+
+ if self.page.something():
+ raise BrowserIncorrectPassword()
+
+ @need_login
+ def iter_something(self):
+ self.something.go()
+ return self.page.iter_something()
diff --git a/tools/cookiecutter/{{cookiecutter.module_name}}/module.py b/tools/cookiecutter/{{cookiecutter.module_name}}/module.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f80f0c9f5989754f23aea6aa7747c2b43d5f760
--- /dev/null
+++ b/tools/cookiecutter/{{cookiecutter.module_name}}/module.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) {{cookiecutter.year}} {{cookiecutter.full_name}}
+#
+# This file is part of a woob module.
+#
+# This woob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This woob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this woob module. If not, see .
+
+# flake8: compatible
+
+from __future__ import unicode_literals
+
+from woob.capabilities.{{cookiecutter.capability | replace('Cap', '') | lower}} import {{cookiecutter.capability}}
+from woob.tools.backend import Module, BackendConfig
+from woob.tools.value import ValueBackendPassword
+
+from .browser import {{cookiecutter.class_prefix}}Browser
+
+
+__all__ = ["{{cookiecutter.class_prefix}}Module"]
+
+
+class {{cookiecutter.class_prefix}}Module(Module, {{cookiecutter.capability}}):
+ NAME = "{{cookiecutter.module_name}}"
+ DESCRIPTION = "{{cookiecutter.site_name}}"
+ MAINTAINER = "{{cookiecutter.full_name}}"
+ EMAIL = "{{cookiecutter.email}}"
+ LICENSE = "LGPLv3+"
+ VERSION = "3.1"
+
+ BROWSER = {{cookiecutter.class_prefix}}Browser
+
+ CONFIG = BackendConfig(
+ ValueBackendPassword("login", label="Username", masked=False),
+ ValueBackendPassword("password", label="Password"),
+ )
+
+ def create_default_browser(self):
+ return self.create_browser(
+ self.config["login"].get(),
+ self.config["password"].get()
+ )
+
+ def iter_something(self):
+ return self.browser.iter_something()
diff --git a/tools/cookiecutter/{{cookiecutter.module_name}}/pages.py b/tools/cookiecutter/{{cookiecutter.module_name}}/pages.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e651892ba79e32131f603371b602dfba28043d2
--- /dev/null
+++ b/tools/cookiecutter/{{cookiecutter.module_name}}/pages.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) {{cookiecutter.year}} {{cookiecutter.full_name}}
+#
+# This file is part of a woob module.
+#
+# This woob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This woob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this woob module. If not, see .
+
+# flake8: compatible
+
+from __future__ import unicode_literals
+
+from woob.browser.elements import method, ListElement, ItemElement
+from woob.browser.filters.html import AbsoluteLink
+from woob.browser.filters.standard import (
+ CleanText, CleanDecimal, Date,
+)
+from woob.browser.pages import LoggedPage, HTMLPage
+
+
+class LoginPage(HTMLPage):
+ def login(self, username, password):
+ form = self.get_form(id="login")
+ form["login"] = username
+ form["password"] = password
+ form.submit()
+
+
+class SomethingPage(LoggedPage, HTMLPage):
+ @method
+ class iter_something(ListElement):
+ item_xpath = "//div[@id='something']"
+
+ class item(ItemElement):
+ klass = Something
+
+ obj_label = CleanText(".//span[has-class('col-label')]")
+ obj_price = CleanDecimal.SI(".//span[has-class('col-amount')]")
+ obj_date = Date(CleanText(".//span[has-class('col-date')]"))
+ obj_url = AbsoluteLink(".//a")
diff --git a/tools/cookiecutter/{{cookiecutter.module_name}}/test.py b/tools/cookiecutter/{{cookiecutter.module_name}}/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5c5f88db9c0c86d56ac0b499bebf539c9f2b2c2
--- /dev/null
+++ b/tools/cookiecutter/{{cookiecutter.module_name}}/test.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) {{cookiecutter.year}} {{cookiecutter.full_name}}
+#
+# This file is part of a woob module.
+#
+# This woob module is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This woob module is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this woob module. If not, see .
+
+# flake8: compatible
+
+from woob.tools.test import BackendTest
+
+
+class TwitterTest(BackendTest):
+ MODULE = "{{cookiecutter.module_name}}"
+
+ def test_something(self):
+ n = -1
+ for obj, n in zip(self.backend.iter_something(), range(20)):
+ assert obj.label
+ assert obj.price
+ assert obj.url.startswith(self.backend.browser.BASEURL)
+
+ assert n > -1
diff --git a/tools/pyflakes.sh b/tools/pyflakes.sh
index aadf2440dad5c0cb53ffae98f36e33de640fde1c..e9efc5ad194c5c33f075b0d0b8a31cb130c77684 100755
--- a/tools/pyflakes.sh
+++ b/tools/pyflakes.sh
@@ -10,7 +10,7 @@ MODULE_FILES=$(git ls-files modules|grep '\.py$')
# Takes PYFILES from env, if empty use all git tracked files
: ${PYFILES:=}
if [ -z "${PYFILES}" ]; then
- PYFILES="$(git ls-files | grep '^scripts\|\.py$'|grep -v boilerplate_data|grep -v stable_backport_data|grep -v '^modules'|grep -v '^contrib')"
+ PYFILES="$(git ls-files | grep '^scripts\|\.py$'|grep -v boilerplate_data|grep -v stable_backport_data|grep -v '^modules'|grep -v '^contrib'|grep -v cookiecutter)"
PYFILES="$PYFILES $MODULE_FILES"
fi