├── .gitignore ├── LICENSE ├── README └── make-flaskext.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyo 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Flask Extension Wizard 2 | ====================== 3 | 4 | A script that can generate Flask Extensions. Just make sure to 5 | have Flask installed before running that script. It will prompt 6 | you for a few things and then generate the basic stuff for you. 7 | 8 | If you have Sphinx installed, it can create Sphinx documentation. 9 | (It will automatically set up the Sphinx themes as a Git submodule 10 | if you are using Git.) 11 | 12 | If you have Git or Mercurial installed, it can initialize a 13 | repository, and add a link to the development version if you plan 14 | to host it on Github, Gitorious, or Bitbucket. 15 | 16 | By default, extensions are listed as BSD licensed, but you can 17 | select the MIT license or use a different license by selecting 18 | "none" when the wizard asks. 19 | 20 | Installation with a fresh virtualenv: 21 | 22 | $ virtualenv env 23 | $ . env/bin/activate 24 | $ easy_install Flask Sphinx 25 | $ wget http://bit.ly/flask-extension-wizard 26 | $ python make-flaskext.py 27 | 28 | Then follow the instructions. 29 | -------------------------------------------------------------------------------- /make-flaskext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | make-flaskext 4 | ~~~~~~~~~~~~~ 5 | 6 | Little helper script that helps creating new flask extensions. 7 | 8 | :copyright: (c) 2010 by Armin Ronacher. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | from __future__ import with_statement 12 | import re 13 | import os 14 | import sys 15 | import getpass 16 | from datetime import datetime 17 | from subprocess import Popen 18 | 19 | from jinja2 import Template 20 | from werkzeug import url_quote 21 | 22 | 23 | _sep_re = re.compile(r'[\s.,;_-]+') 24 | 25 | 26 | SPHINX_THEME_REPO = 'git://github.com/mitsuhiko/flask-sphinx-themes.git' 27 | FILE_HEADER_TEMPLATE = Template(u'''\ 28 | # -*- coding: utf-8 -*- 29 | """ 30 | {{ module }} 31 | {{ '~' * module|length }} 32 | 33 | Description of the module goes here... 34 | 35 | :copyright: (c) {{ year }} by {{ name }}. 36 | :license: {{ license }}, see LICENSE for more details. 37 | """ 38 | ''') 39 | MIT_LICENSE_TEMPLATE = Template(u'''\ 40 | Copyright (c) {{ year }} {{ name }} 41 | 42 | Permission is hereby granted, free of charge, to any person 43 | obtaining a copy of this software and associated documentation 44 | files (the "Software"), to deal in the Software without 45 | restriction, including without limitation the rights to use, 46 | copy, modify, merge, publish, distribute, sublicense, and/or sell 47 | copies of the Software, and to permit persons to whom the 48 | Software is furnished to do so, subject to the following 49 | conditions: 50 | 51 | The above copyright notice and this permission notice shall be 52 | included in all copies or substantial portions of the Software. 53 | 54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 55 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 56 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 57 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 58 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 59 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 60 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 61 | OTHER DEALINGS IN THE SOFTWARE. 62 | ''') 63 | BSD_LICENSE_TEMPLATE = Template(u'''\ 64 | Copyright (c) {{ year }} by {{ name }}. 65 | 66 | Some rights reserved. 67 | 68 | Redistribution and use in source and binary forms, with or without 69 | modification, are permitted provided that the following conditions are 70 | met: 71 | 72 | * Redistributions of source code must retain the above copyright 73 | notice, this list of conditions and the following disclaimer. 74 | 75 | * Redistributions in binary form must reproduce the above 76 | copyright notice, this list of conditions and the following 77 | disclaimer in the documentation and/or other materials provided 78 | with the distribution. 79 | 80 | * The names of the contributors may not be used to endorse or 81 | promote products derived from this software without specific 82 | prior written permission. 83 | 84 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 85 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 86 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 87 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 88 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 89 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 90 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 91 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 92 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 93 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 94 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 95 | ''') 96 | 97 | SETUP_PY_TEMPLATE = Template(u'''\ 98 | """ 99 | {{ name }} 100 | {{ '-' * name|length }} 101 | 102 | Description goes here... 103 | 104 | Links 105 | ````` 106 | 107 | * `documentation `_ 108 | {% if vcs_host in ('github', 'gitorious', 'bitbucket') -%} 109 | * `development version 110 | {%- if vcs_host == 'github' %} 111 | `_ 112 | {%- elif vcs_host == 'gitorious' %} 113 | `_ 114 | {%- elif vcs_host == 'bitbucket' %} 115 | `_ 116 | {% endif %} 117 | {% endif %} 118 | """ 119 | from setuptools import setup 120 | 121 | 122 | setup( 123 | name={{ name|pprint }}, 124 | version='0.1', 125 | url='', 126 | license={{ license|pprint }}, 127 | author={{ author|pprint }}, 128 | author_email='your-email-here@example.com', 129 | description='', 130 | long_description=__doc__, 131 | packages=['flaskext'], 132 | namespace_packages=['flaskext'], 133 | zip_safe=False, 134 | platforms='any', 135 | install_requires=[ 136 | 'Flask' 137 | ], 138 | classifiers=[ 139 | 'Development Status :: 4 - Beta', 140 | 'Environment :: Web Environment', 141 | 'Intended Audience :: Developers', 142 | {%- if license %} 143 | 'License :: OSI Approved :: {{ license }} License', 144 | {%- endif %} 145 | 'Operating System :: OS Independent', 146 | 'Programming Language :: Python', 147 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 148 | 'Topic :: Software Development :: Libraries :: Python Modules' 149 | ] 150 | ) 151 | ''') 152 | 153 | 154 | def prompt(name, default=None): 155 | prompt = name + (default and ' [%s]' % default or '') 156 | prompt += name.endswith('?') and ' ' or ': ' 157 | while True: 158 | rv = raw_input(prompt) 159 | if rv: 160 | return rv 161 | if default is not None: 162 | return default 163 | 164 | 165 | def prompt_bool(name, default=False): 166 | while True: 167 | rv = prompt(name + '?', default and 'Y' or 'N') 168 | if not rv: 169 | return default 170 | if rv.lower() in ('y', 'yes', '1', 'on', 'true', 't'): 171 | return True 172 | elif rv.lower() in ('n', 'no', '0', 'off', 'false', 'f'): 173 | return False 174 | 175 | 176 | def prompt_choices(name, choices): 177 | while True: 178 | rv = prompt(name + '? - (%s)' % ', '.join(choices), choices[0]) 179 | rv = rv.lower() 180 | if not rv: 181 | return choices[0] 182 | if rv in choices: 183 | if rv == 'none': 184 | return None 185 | else: 186 | return rv 187 | 188 | 189 | def guess_package(name): 190 | """Guess the package name""" 191 | words = [x.lower() for x in _sep_re.split(name)] 192 | words = [x for x in words if x != 'flask'] 193 | return '_'.join(words) or None 194 | 195 | 196 | class Extension(object): 197 | 198 | def __init__(self, name, shortname, author, output_folder, vcs, vcs_host, 199 | license, with_sphinx, sphinx_theme): 200 | self.name = name 201 | self.shortname = shortname 202 | self.author = author 203 | self.output_folder = output_folder 204 | self.vcs = vcs 205 | self.vcs_host = vcs_host 206 | self.license = license 207 | self.with_sphinx = with_sphinx 208 | self.sphinx_theme = sphinx_theme 209 | 210 | def make_folder(self): 211 | os.makedirs(os.path.join(self.output_folder, 'flaskext')) 212 | 213 | def create_files(self): 214 | with open(os.path.join(self.output_folder, 'flaskext', 215 | '__init__.py'), 'w') as f: 216 | f.write("__import__('pkg_resources')." 217 | "declare_namespace(__name__)\n") 218 | with open(os.path.join(self.output_folder, 'flaskext', 219 | self.shortname + '.py'), 'w') as f: 220 | f.write(FILE_HEADER_TEMPLATE.render( 221 | module='flaskext.' + self.shortname, 222 | year=datetime.utcnow().year, 223 | name=self.author, 224 | license=self.license 225 | ).encode('utf-8') + '\n') 226 | with open(os.path.join(self.output_folder, 'LICENSE'), 'w') as f: 227 | if self.license == 'BSD': 228 | f.write(BSD_LICENSE_TEMPLATE.render( 229 | year=datetime.utcnow().year, 230 | name=self.author 231 | ).encode('utf-8') + '\n') 232 | elif self.license == 'MIT': 233 | f.write(MIT_LICENSE_TEMPLATE.render( 234 | year=datetime.utcnow().year, 235 | name=self.author 236 | ).encode('utf-8') + '\n') 237 | with open(os.path.join(self.output_folder, 'README'), 'w') as f: 238 | f.write(self.name + '\n\nDescription goes here\n') 239 | with open(os.path.join(self.output_folder, 'setup.py'), 'w') as f: 240 | f.write(SETUP_PY_TEMPLATE.render( 241 | name=self.name, 242 | urlname=url_quote(self.name), 243 | package='flaskext.' + self.shortname, 244 | author=self.author, 245 | vcs_host=self.vcs_host, 246 | license=self.license 247 | ).encode('utf-8') + '\n') 248 | 249 | def init_vcs(self): 250 | if self.vcs == 'hg': 251 | self.init_hg() 252 | elif self.vcs == 'git': 253 | self.init_git() 254 | 255 | def init_hg(self): 256 | Popen(['hg', 'init'], cwd=self.output_folder).wait() 257 | 258 | def init_git(self): 259 | Popen(['git', 'init'], cwd=self.output_folder).wait() 260 | if self.with_sphinx: 261 | Popen(['git', 'submodule', 'add', SPHINX_THEME_REPO, 262 | 'docs/_themes'], cwd=self.output_folder).wait() 263 | 264 | def init_sphinx(self): 265 | if not self.with_sphinx: 266 | return 267 | docdir = os.path.join(self.output_folder, 'docs') 268 | os.makedirs(docdir) 269 | Popen(['sphinx-quickstart'], cwd=docdir).wait() 270 | if os.path.isfile(os.path.join(docdir, 'source', 'conf.py')): 271 | sphinx_conf_py = os.path.join(docdir, 'source', 'conf.py') 272 | else: 273 | sphinx_conf_py = os.path.join(docdir, 'conf.py') 274 | with open(sphinx_conf_py, 'r') as f: 275 | config = f.read().splitlines() 276 | for idx, line in enumerate(config): 277 | if line.startswith('#sys.path.append'): 278 | config[idx] = "sys.path.append(os.path.abspath('_themes'))" 279 | elif line.startswith('html_theme ='): 280 | config[idx] = 'html_theme = %r' % self.sphinx_theme 281 | elif line == '#html_theme_path = []': 282 | config[idx] = "html_theme_path = ['_themes']" 283 | elif line.startswith('pygments_style ='): 284 | config[idx] = "#pygments_style = 'sphinx'" 285 | with open(sphinx_conf_py, 'w') as f: 286 | f.write('\n'.join(config)) 287 | if not self.vcs == 'git': 288 | print 'Don\'t forget to put the sphinx themes into docs/_themes!' 289 | 290 | 291 | def main(): 292 | if len(sys.argv) not in (1, 2): 293 | print 'usage: make-flaskext.py [output-folder]' 294 | return 295 | print 'Welcome to the Flask Extension Creator Wizard' 296 | print 297 | 298 | while 1: 299 | name = prompt('Extension Name (human readable)') 300 | if 'flask' in name.lower(): 301 | break 302 | if prompt_bool('Warning: It\'s recommended that the extension name ' 303 | 'contains the word "Flask". Continue'): 304 | break 305 | shortname = prompt('Shortname (without flaskext.)', default=guess_package(name)) 306 | author = prompt('Author', default=getpass.getuser()) 307 | license_rv = prompt_choices('License', ('bsd', 'mit', 'none')) 308 | if license_rv == 'bsd': 309 | license = 'BSD' 310 | elif license_rv == 'mit': 311 | license = 'MIT' 312 | else: 313 | license = None 314 | use_sphinx = prompt_bool('Create sphinx documentation', default=True) 315 | sphinx_theme = None 316 | if use_sphinx: 317 | sphinx_theme = prompt('Sphinx theme to use', default='flask_small') 318 | vcs = prompt_choices('Which VCS to use', ('none', 'git', 'hg')) 319 | if vcs is None: 320 | vcs_host = None 321 | elif vcs == 'git': 322 | vcs_host = prompt_choices('Which git host to use', 323 | ('none', 'github', 'gitorious')) 324 | elif vcs == 'hg': 325 | vcs_host = prompt_choices('Which Mercurial host to use', 326 | ('none', 'bitbucket')) 327 | 328 | output_folder = len(sys.argv) == 2 and sys.argv[1] or ('flask-%s' % shortname) 329 | while 1: 330 | folder = prompt('Output folder', default=output_folder) 331 | if os.path.isfile(folder): 332 | print 'Error: output folder is a file' 333 | elif os.path.isdir(folder) and os.listdir(folder): 334 | if prompt_bool('Warning: output folder is not empty. Continue'): 335 | break 336 | else: 337 | break 338 | output_folder = os.path.abspath(folder) 339 | 340 | ext = Extension(name, shortname, author, output_folder, vcs, vcs_host, 341 | license, use_sphinx, sphinx_theme) 342 | ext.make_folder() 343 | ext.create_files() 344 | ext.init_sphinx() 345 | ext.init_vcs() 346 | 347 | 348 | if __name__ == '__main__': 349 | main() 350 | --------------------------------------------------------------------------------