├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── VERSION ├── buildstrap ├── __init__.py ├── buildstrap.py └── templates │ ├── pytest.part.cfg │ └── sphinx.part.cfg ├── doc ├── advanced_usage.md ├── buildout.md ├── buildstrap.rst ├── conf.py ├── index.md ├── modules.rst └── quickstart.md ├── requirements-doc.txt ├── requirements-test.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py └── test_buildstrap.py /.gitignore: -------------------------------------------------------------------------------- 1 | var/ 2 | __pycache__ 3 | *.pyc 4 | .eggs 5 | *.egg-info 6 | bin/ 7 | .installed.cfg 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.4" 4 | - "3.5" 5 | - "3.5-dev" # 3.5 development branch 6 | addons: 7 | apt: 8 | packages: 9 | - pandoc 10 | # command to install dependencies 11 | install: "pip install codeclimate-test-reporter" 12 | # command to run tests 13 | script: "python setup.py test" 14 | addons: 15 | code_climate: 16 | repo_token: f2d3e4012404e1ea57ce15e392308d55b891a5ba8edd136c9e0072131d52e161 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include VERSION 3 | recursive-include buildstrap/templates * 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Buildstrap: generate a buildout config for any \*env project 2 | 3 | [![WTFPL](http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-2.png)](http://wtfpl.org) 4 | [![Python3](https://img.shields.io/pypi/pyversions/buildstrap.svg)](https://pypi.python.org/pypi/buildstrap) 5 | [![Issues](https://img.shields.io/github/issues/guyzmo/buildstrap.svg)](https://github.com/guyzmo/buildstrap) 6 | [![Build](https://travis-ci.org/guyzmo/buildstrap.svg)](https://travis-ci.org/guyzmo/buildstrap) 7 | [![Code Climate](https://codeclimate.com/github/guyzmo/buildstrap/badges/gpa.svg)](https://codeclimate.com/github/guyzmo/buildstrap) 8 | [![Coverage](https://codeclimate.com/github/guyzmo/buildstrap/badges/coverage.svg)](https://codeclimate.com/github/guyzmo/buildstrap) 9 | 10 | There's pyenv, pyvenv, venv, virtualenv… and who knows how many other ways to 11 | deal with development of python programs in a per-project self-contained 12 | manner. 13 | 14 | While most of the python community tried to keep up, and got their shell 15 | configuration or global pip changing regularly, some have been quietly enjoying 16 | python development the same way for the last ten years, using [buildout] for 17 | their development. 18 | 19 | Though, it's a fact that buildout is not the standard way to do things, even if 20 | it's a very convenient tool. So to keep your repositories compatible with most 21 | \*env tools available — or get buildout with other projects. I wrote this tool 22 | to make it easy to create a buildout environment within the project. 23 | 24 | [buildout]:https://github.com/buildout/buildout/ 25 | 26 | # Quickstart Guide 27 | 28 | Here we'll see the most common usages, and refer to [the full documentation for 29 | more details][doc]. 30 | 31 | [doc]:https://buildstrap.readthedocs.io/ 32 | 33 | ## Usage 34 | 35 | when you got a repository that has requirements files, at the root of your project's 36 | directory, call buildstrap using: 37 | 38 | ``` 39 | % buildstrap run project requirements.txt 40 | ``` 41 | 42 | where `project` as second argument is the name of the package as you've set it 43 | up in your `setup.py` — and as you'd import it from other python code. 44 | 45 | Running that command will generate the `buildout.cfg` file, and run `buildout` 46 | in your current directory. Then you'll find all your scripts available in the 47 | newly created `bin` directory of your project. 48 | 49 | If you have several `requirements.txt` files, depending on the task you want to 50 | do, it's easy: 51 | 52 | ``` 53 | % buildstrap run project -p pytest -p sphinx requirements.txt requirements-test.txt requirements-doc.txt 54 | ``` 55 | 56 | which will create three sections in your `buildout.cfg` file, and get all the 57 | appropriate dependencies. 58 | 59 | Here's a real life example: 60 | 61 | ``` 62 | % git hub clone kennethreitz/requests # cf 'Nota Bene' 63 | % cd requests 64 | % buildstrap run requests requirements.txt 65 | … 66 | % bin/py.test 67 | … (look at the tests result) 68 | % bin/python3 69 | >>> import requests 70 | >>> 71 | ``` 72 | 73 | or another one: 74 | 75 | ``` 76 | % git hub clone jkbrzt/httpie # cf 'Nota Bene' 77 | % cd httpie 78 | % buildstrap run httpie requirements-dev.txt 79 | … 80 | % bin/py.test 81 | … (look at the tests result) 82 | % bin/http --version 83 | 1.0.0-dev 84 | ``` 85 | 86 | ## Installation 87 | 88 | it's as easy as any other python program: 89 | 90 | ``` 91 | % pip install buildstrap 92 | ``` 93 | 94 | or from the sources: 95 | 96 | ``` 97 | % git hub clone guyzmo/buildstrap 98 | % cd buildstrap 99 | % python3 setup.py install 100 | ``` 101 | 102 | ## Development 103 | 104 | for development you just need to do: 105 | 106 | ``` 107 | % pip install buildstrap 108 | % git clone https://github.com/guyzmo/buildstrap 109 | % cd buildstrap 110 | % builstrap run buildstrap -p pytest -p sphinx requirements.txt requirement-test.txt requirement-doc.txt 111 | … 112 | % bin/buildstrap 113 | ``` 114 | 115 | Yeah, I'm being evil here 😈 116 | 117 | You can have a look at the [sources documentation][srcdoc]. 118 | 119 | [srcdoc]:https://buildstrap.readthedocs.io/en/latest/buildstrap.html 120 | 121 | ## Nota Bene 122 | 123 | You might wonder where does the `git hub clone` command comes from, and I'm 124 | using here another project I wrote: [guyzmo/git-repo](https://github.com/guyzmo/git-repo). 125 | 126 | Simply put, `git hub clone user/project` is equivalent to `git clone https://github.com/user/project`. 127 | 128 | ## License 129 | 130 | Copyright © 2016 Bernard `Guyzmo` Pratz 131 | This work is free. You can redistribute it and/or modify it under the 132 | terms of the Do What The Fuck You Want To Public License, Version 2, 133 | as published by Sam Hocevar. See the LICENSE file for more details. 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.0 2 | -------------------------------------------------------------------------------- /buildstrap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guyzmo/buildstrap/d922cbff2df808086d0792ed356ec146f4531bab/buildstrap/__init__.py -------------------------------------------------------------------------------- /buildstrap/buildstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | Buildstrap: generate and run buildout in your projects :: 5 | 6 | Usage: {} [-v...] [options] [run|show|debug|generate] [-p part...] ... 7 | 8 | Options: 9 | run run buildout once buildout.cfg has been generated 10 | show show the buildout.cfg (same as using `-o -`) 11 | debug print internal representation of buildout config 12 | generate create the buildout.cfg file (default action) 13 | use this name for the package being developed 14 | use this requirements file as main requirements 15 | -p,--part choose part template to use (use "list" to show all) 16 | -i,--interpreter use this python version 17 | -o,--output file to output [default: buildout.cfg] 18 | -r,--root path to the project root (where buildout.cfg will 19 | be generated) (defaults to ./) 20 | -s,--src path to the sources (default is same as root path) 21 | relative to the root path if not absolute 22 | -e,--env path to the environment data [default: var] 23 | relative to directory if not absolute 24 | -b,--bin path to the bin directory [default: bin] 25 | relative to directory if not absolute 26 | -f,--force force overwrite output file if it exists 27 | -c,--config path to the configuration directory 28 | [default: ~/.config/buildstrap] 29 | -v,--verbose increase verbosity 30 | -h,--help show this message 31 | --version show version 32 | 33 | For more detailed help, please read the documentation 34 | on https://readthedocs.org/buildstrap 35 | ''' 36 | 37 | import os, sys 38 | 39 | from contextlib import contextmanager 40 | from collections import OrderedDict 41 | from configparser import ConfigParser 42 | from pprint import pprint 43 | from docopt import docopt 44 | 45 | from zc.buildout.configparser import parse 46 | from zc.buildout.buildout import main as buildout 47 | 48 | import pkg_resources 49 | 50 | __version__ = pkg_resources.require('buildstrap')[0].version 51 | 52 | class ListBuildout(list): 53 | '''Makes it possible to print a list the way buildout expects it. 54 | 55 | Because buildout uses a custom built parser for parsing ini style files, 56 | that has a major difference in list handling from the standard config 57 | parser. 58 | 59 | The standard configparser doesn't anything about lists, and outputs 60 | python's internal representation of strings: ``['a', 'b', 'c']`` as values. 61 | And the standard configparser consider multiline values as a multiline 62 | string. 63 | 64 | On the other hand, buildout considers multiline values as lists, one value 65 | per line. 66 | 67 | This class uses a context manager to define the behaviour of the string conversion 68 | method. The default behaviour is the same as the standard list. But when within 69 | the context of the ``generate_context`` method, it prints lists as multiline string, 70 | one value per line, the way buildout expects it. 71 | ''' 72 | _generating_state = False 73 | 74 | @classmethod 75 | @contextmanager 76 | def generate_context(cls): 77 | '''Context manager to change the string conversion behaviour on this class''' 78 | cls._generating_state = True 79 | yield 80 | cls._generating_state = False 81 | 82 | def __str__(self): 83 | '''Replaces standard behaviour of list string output, so it prints as buildout 84 | expects when generating the buildout file''' 85 | if self._generating_state is True: 86 | if len(self) == 1: 87 | return self[0] 88 | else: 89 | return '\n'.join(self) 90 | else: 91 | return super(ListBuildout, self).__str__() 92 | 93 | def build_part_target(target, packages=list(), interpreter=None): 94 | '''Generates a part to run the currenctly develop package 95 | 96 | This will output a part, that will make a script based on the current package 97 | developed, using the ``zc.recipe.egg`` recipe, to populate the environment. 98 | The generated part follows the following template:: 99 | 100 | [] 101 | recipe=zc.recipe.egg 102 | eggs= 103 | interpreter= 104 | 105 | If no ``packages`` argument is given, the list only contains the reference to 106 | the requirements egg list, otherwise the list of packages gets appended. 107 | If no interpreter argument is given, the directive is ignored. 108 | 109 | Args: 110 | target: name to be used for the part 111 | interpreter: if given, setup the interpreter directive, using the name 112 | of a python interpreter as a string. 113 | packages: if given, adds that package to the list of requirements. 114 | 115 | Raises: 116 | TypeError: if the packages is not a list of string 117 | 118 | Returns: 119 | dict representation of the part 120 | ''' 121 | if not isinstance(packages, list): 122 | raise TypeError('packages argument should be a list!') 123 | 124 | eggs = [ '${buildout:requirements-eggs}' ] 125 | eggs += packages 126 | 127 | part = { 128 | target: OrderedDict([ 129 | ('recipe' , 'zc.recipe.egg'), 130 | ('eggs' , ListBuildout(eggs)), 131 | ]) 132 | } 133 | 134 | if interpreter: 135 | part[target].update({'interpreter': interpreter}) 136 | 137 | return part 138 | 139 | 140 | def list_part_templates(config_path): 141 | '''Iterates over the available part templates 142 | 143 | Will get through both package's templates path and user config path to 144 | check for ``.part.cfg`` files. 145 | 146 | Args: 147 | config_path: path to the user's part template directory 148 | 149 | Returns: 150 | iterator over the list of templates 151 | 152 | ''' 153 | user_config_path = os.path.join(os.path.expanduser(config_path)) 154 | pkg_config_path = os.path.join(os.path.dirname(__file__), 'templates') 155 | 156 | templates = [] 157 | if os.path.exists(pkg_config_path): 158 | templates += os.listdir(os.path.join(pkg_config_path)) 159 | print('Using parts from {}'.format(pkg_config_path), file=sys.stderr) 160 | if os.path.exists(user_config_path): 161 | templates += os.listdir(user_config_path) 162 | print('Using parts from {}'.format(user_config_path), file=sys.stderr) 163 | 164 | for fname in templates: 165 | if 'list' in fname: 166 | print('Warning: a part template named list.cfg exists, and cannot be called. Please change its name!', file=sys.stderr) 167 | if '.part.cfg' in fname: 168 | yield fname.replace('.part.cfg', '') 169 | else: 170 | print('Warning: file named {} does not end with .part.cfg and is ignored!'.format(fname), file=sys.stderr) 171 | 172 | def build_part_template(name, config_path): 173 | '''Creates a part out of a template file 174 | 175 | Will resolve a part file based on its name, by looking through both package's 176 | static directory, and through user defined configuration path. 177 | 178 | The template file will feature a section (which name is the same as the file name) 179 | and will be parsed, and then added to the buildout file *as is*. It will also be 180 | named with the ``.part.cfg`` extension. 181 | 182 | Args: 183 | name: name of the template file (without extension) 184 | config_path: directory where to look for the template file 185 | 186 | Returns: 187 | dict representation of a part 188 | 189 | Raises: 190 | FileNotFoundError if no template can be found. 191 | ''' 192 | template_name = '{}.part.cfg'.format(name) 193 | template_path = os.path.join(os.path.expanduser(config_path)) 194 | 195 | try: 196 | template_file = open(template_path, 'r') 197 | except FileNotFoundError: 198 | try: 199 | template_path = os.path.join(os.path.dirname(__file__), 'templates', template_name) 200 | template_file = open(template_path, 'r') 201 | except FileNotFoundError: 202 | template_file = None 203 | 204 | if not template_file: 205 | raise FileNotFoundError('Missing template file {}.part.cfg in {}'.format(name, config_path)) 206 | 207 | res = parse(open(template_path, 'r'), name) 208 | # make items order predictible 209 | for k,v in res.items(): 210 | if isinstance(v, dict): 211 | res[k] = OrderedDict(sorted(v.items(), key=lambda t: t[0])) 212 | return res 213 | 214 | def build_part_buildout(root_path=None, src_path=None, env_path=None, bin_path=None): 215 | '''Generates the buildout part 216 | 217 | This part is the entry point of a buildout configuration file, setting up 218 | general values for the environment. Here we setup paths and defaults for 219 | buildout's behaviour. Please refer to buildout documentation for more. 220 | 221 | This will output a buildout header that can be considered as a good start:: 222 | 223 | [buildout] 224 | newest=false 225 | parts= 226 | package= 227 | extensions=gp.vcsdevelopc 228 | directory=. 229 | develop=${buildout:directory} 230 | eggs-directory=${buildout:directory}/var/eggs 231 | develop-eggs-directory=${buildout:directory}/var/develop-eggs 232 | develop-dir=${buildout:directory}/var/develop 233 | parts-directory=${buildout:directory}/var/parts 234 | requirements= 235 | 236 | Parameter ``root_path`` will change the path to the project's root, which is where 237 | the enviroment will be based on. If you're placing the ``buildout.cfg`` file in another 238 | directory than the root of the project, set it to the path that can get you from 239 | the buildout.cfg into the project, and it will all work ok. 240 | 241 | Parameter ``src_path`` will change the path to the sources, so if you've got your 242 | sources in ``./src``, you can set it up to src and it will generate:: 243 | 244 | develop=./src 245 | 246 | Beware that all non-absolute paths given to ``src_path`` are relative to the 247 | ``root_path``. 248 | 249 | For parameter ``bin_path`` and ``env_path``, it will respectively change path to the 250 | generated ``bin`` directory and ``env`` directory, after running buildout. 251 | 252 | Args: 253 | root_path: path string to the root of the project (from which all other paths are relative to) 254 | src_path: path string to the sources (where ``setup.py`` is) 255 | env_path: path string to the environment (where dependencies are downloaded) 256 | bin_path: path string to the runnable scripts 257 | 258 | Returns: 259 | the buildout part as a dict 260 | ''' 261 | buildout = OrderedDict() 262 | buildout['newest'] = 'false' 263 | buildout['parts'] = '' 264 | buildout['package'] = '' 265 | buildout['extensions'] = 'gp.vcsdevelop' 266 | if root_path: 267 | buildout['directory'] = root_path 268 | if not env_path: 269 | env_path = 'var' 270 | if not os.path.isabs(env_path): 271 | env_path = os.path.join('${buildout:directory}', env_path) 272 | if not bin_path: 273 | bin_path = 'bin' 274 | if not os.path.isabs(bin_path): 275 | bin_path = os.path.join('${buildout:directory}', bin_path) 276 | buildout['develop'] = src_path if src_path else '.' 277 | buildout['eggs-directory'] = os.path.join(env_path, 'eggs') 278 | buildout['develop-eggs-directory'] = os.path.join(env_path, 'develop-eggs') 279 | buildout['parts-directory'] = os.path.join(env_path, 'parts') 280 | buildout['develop-dir'] = os.path.join(env_path, 'develop') 281 | buildout['bin-directory'] = bin_path 282 | buildout['requirements'] = ListBuildout([]) 283 | return {'buildout': buildout} 284 | 285 | 286 | def build_parts(packages, requirements, part_templates=[], interpreter=None, 287 | config_path=None, root_path='.', src_path=None, env_path=None, bin_path=None): 288 | '''Builds up the different parts of the buildout configuration 289 | 290 | this is the workhorse of this code. It will build and return an internal 291 | dict representation of a buildout configuration, following the values given 292 | by the arguments. The buildout configuration can be seen as a succession of 293 | parts, each one being a section in the configuration file. For more, please 294 | refer to buildout's documentation. 295 | 296 | First, it generates the ``[buildout]`` part within the dict representation. 297 | Within it, it will setup the ``packages`` value so we keep track of which 298 | packages you want to build, the ``requirements`` value will be used to find 299 | and download all the eggs that are needed as dependencies. the ``parts`` list 300 | will keep track of each generated part, only one part being generated for the 301 | code under development (even if there are several packages). 302 | 303 | The first argument will define the first part's name (the one that will be 304 | used to generate a script if an entry point has been defined within the ``setup.py``). 305 | Thus, it will append the package name to the list of packages within the ``[buildout]`` 306 | section, and be added to the list of eggs that will be run:: 307 | 308 | [buildout] 309 | package = marvin 310 | parts = marvin 311 | … 312 | 313 | 314 | [marvin] 315 | recipe = zc.recipe.egg 316 | eggs = ${buildout:requirements-eggs} 317 | marvin 318 | 319 | The second argument is the list of requirements to be parsed and fed to ``gp.vcsdevelop`` 320 | so it can work out downloading all your dependencies:: 321 | 322 | [buildout] 323 | requirements = requirements.txt 324 | … 325 | 326 | Both can be lists (or comma separated list — as a *string*) of package names and 327 | requirements files, so if you give packages and requirements being respectively: 328 | 329 | * ``dent,prefect,beeblebrox`` and 330 | * ``requirements.txt,requirements-dev.txt`` 331 | 332 | it will generate:: 333 | 334 | [buildout] 335 | … 336 | parts = marvin 337 | package = marvin prefect beeblebrox 338 | requirements = requirements.txt 339 | requirements-dev.txt 340 | 341 | [marvin] 342 | recipe = zc.recipe.egg 343 | eggs = ${buildout:requirements-eggs} 344 | marvin 345 | 346 | The third argument enables to load a part template. It will load the part from 347 | the static path within the package, or from ``config_path``, which defaults to 348 | the user's home config directory. 349 | 350 | Args: 351 | packages: the list of packages to target as first part (list or comma separated string) 352 | requirements: the list of requirements to target as first part (list or comma separated string) 353 | part_templates: list of templates to load 354 | interpreter: string name of the python interpreter to use 355 | config_path: path string to the configuration directory where to find the template parts files. 356 | root_path: path string to the root of the project (from which all other paths are relative to) 357 | src_path: path string to the sources (where ``setup.py`` is) 358 | env_path: path string to the environment (where dependencies are downloaded) 359 | bin_path: path string to the runnable scripts 360 | 361 | Returns: 362 | OrderedDict instance configured with all parts. 363 | ''' 364 | parts = OrderedDict() 365 | targets = [] 366 | 367 | if not isinstance(packages, list): 368 | packages = packages.split(',') 369 | 370 | if not isinstance(requirements, list): 371 | requirements = requirements.split(',') 372 | 373 | if len(packages) == 0: 374 | raise ValueError("There shall be at least one package to setup.") 375 | 376 | if len(requirements) == 0: 377 | raise ValueError("There shall be at least one requirement to setup.") 378 | 379 | first_part_name = packages[0] 380 | 381 | parts.update(build_part_buildout(root_path, src_path, env_path, bin_path)) 382 | 383 | # build main package part 384 | parts.update(build_part_target(first_part_name, packages, interpreter)) 385 | targets.append(first_part_name) 386 | 387 | parts.update(build_part_target(first_part_name, packages, interpreter)) 388 | 389 | for template_name in part_templates or []: 390 | parts[template_name] = build_part_template(template_name, config_path)[template_name] 391 | targets.append(template_name) 392 | 393 | for r in requirements: 394 | parts['buildout']['requirements'] += ListBuildout([os.path.join('${buildout:develop}', r)]) 395 | parts['buildout']['parts'] = ListBuildout(targets) 396 | parts['buildout']['package'] = ' '.join(packages) 397 | 398 | return parts 399 | 400 | def generate_buildout_config(parts, output, force=False): 401 | '''Generates the buildout configuration 402 | 403 | Using the custom ``ListBuildout`` context, lists will be printed as multilines. 404 | If output is set to ``-`` it will print to stdout the file. 405 | 406 | Args: 407 | parts: dict based representation of the buildout file to generate 408 | output: name of the file to output 409 | force: if set, it won't care whether the file exists 410 | 411 | Raises: 412 | FileExistsError: when a file already exists. 413 | ''' 414 | with ListBuildout.generate_context(): 415 | parser = ConfigParser() 416 | parser.read_dict(parts) 417 | 418 | if output == '-': 419 | parser.write(sys.stdout) 420 | return 421 | 422 | if not force and os.path.exists(output): 423 | raise FileExistsError('\n'.join([ 424 | 'Cannot overwrite {}: file already exists! Use --force if necessary.'.format(output), 425 | 'As a buildout configuration exists, you might want to run buildout directly!' 426 | ])) 427 | 428 | with open(output, 'w') as out: 429 | parser.write(out) 430 | 431 | def buildstrap(args): 432 | '''Parses the command line arguments, build the parts, generate the config and runs buildout 433 | 434 | refer to the __doc__ of this module for all arguments. 435 | 436 | Args: 437 | args: arguments to parse 438 | 439 | Returns: 440 | 0 on success, 1 otherwise 441 | ''' 442 | try: 443 | if args['--verbose'] >= 2: # pragma: no cover 444 | print(args, file=sys.stderr) 445 | 446 | parts = build_parts( 447 | args[''], 448 | args[''], 449 | args['--part'], 450 | args['--interpreter'], 451 | args['--config'], 452 | args['--root'], 453 | args['--src'], 454 | args['--env'], 455 | args['--bin']) 456 | 457 | if args['debug']: 458 | pprint(parts) 459 | return 0 460 | 461 | if args['show']: 462 | args['--output'] = '-' 463 | 464 | generate_buildout_config(parts, args['--output'], args['--force']) 465 | 466 | if args['run']: 467 | buildout(['-c', args['--output']]) 468 | 469 | return 0 470 | except Exception as err: # pragma: no cover 471 | print('Fatal error: {}'.format(err), file=sys.stderr) 472 | if args['--verbose']: 473 | print('-----------------------------------', file=sys.stderr) 474 | import traceback 475 | traceback.print_exc() 476 | return 1 477 | 478 | 479 | def run(): # pragma: no cover 480 | '''Parses arguments, gets current command name and version number''' 481 | sys.exit(buildstrap(docopt(__doc__.format(os.path.basename(sys.argv[0])), 482 | version='Buildstrap v{}'.format(__version__)))) 483 | 484 | 485 | if __name__ == "__main__": # pragma: no cover 486 | '''well there's always a good place to start''' 487 | run() 488 | -------------------------------------------------------------------------------- /buildstrap/templates/pytest.part.cfg: -------------------------------------------------------------------------------- 1 | [pytest] 2 | recipe = zc.recipe.egg 3 | arguments = 4 | ['--cov={}/{}'.format('${buildout:develop}', package) for package in '${buildout:package}'.split(',')] \ 5 | +['--cov-report', 'term-missing', 'tests']+sys.argv[1:] 6 | eggs = ${buildout:requirements-eggs} 7 | -------------------------------------------------------------------------------- /buildstrap/templates/sphinx.part.cfg: -------------------------------------------------------------------------------- 1 | [sphinx] 2 | recipe = collective.recipe.sphinxbuilder 3 | eggs = ${buildout:requirements-eggs} 4 | source = ${buildout:directory}/doc 5 | build = ${buildout:directory}/doc/_build 6 | -------------------------------------------------------------------------------- /doc/advanced_usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ``` 4 | Usage: buildstrap [-v...] [options] [run|show|debug|generate] [-p part...] ... 5 | 6 | Options: 7 | run run buildout once buildout.cfg has been generated 8 | show show the buildout.cfg (same as using `-o -`) 9 | debug print internal representation of buildout config 10 | generate create the buildout.cfg file (default action) 11 | use this name for the package being developed 12 | use this requirements file as main requirements 13 | -p,--part choose part template to use (use "list" to show all) 14 | -i,--interpreter use this python version 15 | -o,--output file to output [default: buildout.cfg] 16 | -r,--root path to the project root (where buildout.cfg will 17 | be generated) (defaults to ./) 18 | -s,--src path to the sources (default is same as root path) 19 | relative to the root path if not absolute 20 | -e,--env path to the environment data [default: var] 21 | relative to directory if not absolute 22 | -b,--bin path to the bin directory [default: bin] 23 | relative to directory if not absolute 24 | -f,--force force overwrite output file if it exists 25 | -c,--config path to the configuration directory 26 | [default: ~/.config/buildstrap] 27 | -v,--verbose increase verbosity 28 | -h,--help show this message 29 | --version show version 30 | ``` 31 | 32 | # Multiple requirements.txt 33 | 34 | Many projects offer multiple `requirements.txt` files, one for each task of 35 | the development cycle (which usually are running, testing, documenting). 36 | 37 | Well, just tell buildstrap what the extra requirements are: 38 | 39 | ``` 40 | % buildstrap run buildstrap -p pytest -p sphinx requirements.txt requirements-doc.txt requirements-test.txt 41 | ``` 42 | 43 | and that will generate the following buildout.cfg configuration: 44 | 45 | ``` 46 | [buildout] 47 | newest = false 48 | parts = buildstrap 49 | pytest 50 | sphinx 51 | package = buildstrap 52 | extensions = gp.vcsdevelop 53 | develop = . 54 | eggs-directory = ${buildout:directory}/var/eggs 55 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 56 | parts-directory = ${buildout:directory}/var/parts 57 | develop-dir = ${buildout:directory}/var/develop 58 | bin-directory = ${buildout:directory}/bin 59 | requirements = ${buildout:develop}/requirements.txt 60 | ${buildout:develop}/requirements-doc.txt 61 | ${buildout:develop}/requirements-test.txt 62 | 63 | [buildstrap] 64 | recipe = zc.recipe.egg 65 | eggs = ${buildout:requirements-eggs} 66 | buildstrap 67 | 68 | [pytest] 69 | arguments = ['--cov={}/{}'.format('${buildout:develop}', package) for package in '${buildout:pack 70 | age}'.split(',')] \ 71 | +['--cov-report', 'term-missing', 'tests']+sys.argv[1:] 72 | eggs = ${buildout:requirements-eggs} 73 | recipe = zc.recipe.egg 74 | 75 | [sphinx] 76 | eggs = ${buildout:requirements-eggs} 77 | source = ${buildout:directory}/doc 78 | recipe = collective.recipe.sphinxbuilder 79 | build = ${buildout:directory}/doc/_build 80 | ``` 81 | 82 | and you'll find all the tools you'll need in bin: 83 | 84 | ``` 85 | % ls bin 86 | buildout cm2html cm2man cm2xetex py.test sphinx sphinx-autogen sphinx-quickstart 87 | buildstrap cm2latex cm2pseudoxml cm2xml py.test-2.7 sphinx-apidoc sphinx-build 88 | ``` 89 | 90 | # Multiple packages 91 | 92 | Some projects will include several packages in the sources, so to support that, just list 93 | all your packages as a comma seperated list, and they will all be included: 94 | 95 | ``` 96 | % buildstrap show dent,prefect,beeblebox requirements.txt 97 | [buildout] 98 | newest = false 99 | parts = dent 100 | package = dent prefect beeblebox 101 | extensions = gp.vcsdevelop 102 | develop = . 103 | eggs-directory = ${buildout:directory}/var/eggs 104 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 105 | parts-directory = ${buildout:directory}/var/parts 106 | develop-dir = ${buildout:directory}/var/develop 107 | bin-directory = ${buildout:directory}/bin 108 | requirements = ${buildout:develop}/requirements.txt 109 | 110 | [dent] 111 | recipe = zc.recipe.egg 112 | eggs = ${buildout:requirements-eggs} 113 | dent 114 | prefect 115 | beeblebox 116 | ``` 117 | 118 | Control the output 119 | ------------------ 120 | 121 | If you want to only generate the `buildout.cfg` file, simply use buildstrap with 122 | no subcommand, and you'll get it in your current directory! 123 | 124 | ``` 125 | % buildstrap slartibartfast requirements.txt 126 | % cat buildout.cfg 127 | [buildout] 128 | newest = false 129 | parts = slartibartfast 130 | package = slartibartfast 131 | extensions = gp.vcsdevelop 132 | develop = . 133 | eggs-directory = ${buildout:directory}/var/eggs 134 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 135 | parts-directory = ${buildout:directory}/var/parts 136 | develop-dir = ${buildout:directory}/var/develop 137 | bin-directory = ${buildout:directory}/bin 138 | requirements = ${buildout:develop}/requirements.txt 139 | 140 | [slartibartfast] 141 | recipe = zc.recipe.egg 142 | eggs = ${buildout:requirements-eggs} 143 | slartibartfast 144 | 145 | ``` 146 | 147 | but if you want to just test the command and print the configuration to stdout, 148 | without it doing nothing, use the `show` subcommand: 149 | 150 | ``` 151 | % buildstrap show slartibartfast requirements.txt 152 | [buildout] 153 | newest = false 154 | parts = slartibartfast 155 | package = slartibartfast 156 | extensions = gp.vcsdevelop 157 | develop = . 158 | eggs-directory = ${buildout:directory}/var/eggs 159 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 160 | parts-directory = ${buildout:directory}/var/parts 161 | develop-dir = ${buildout:directory}/var/develop 162 | bin-directory = ${buildout:directory}/bin 163 | requirements = ${buildout:develop}/requirements.txt 164 | 165 | [slartibartfast] 166 | recipe = zc.recipe.egg 167 | eggs = ${buildout:requirements-eggs} 168 | slartibartfast 169 | 170 | ``` 171 | 172 | and if you want to write the `buildout.cfg` as another file, you can either redirect 173 | the show command with a pipe, or use the `--output` argument: 174 | 175 | ``` 176 | % buildstrap -o foobar.cfg slartibartfast requirements.txt 177 | % cat foobar.cfg 178 | [buildout] 179 | newest = false 180 | parts = slartibartfast 181 | package = slartibartfast 182 | extensions = gp.vcsdevelop 183 | develop = . 184 | eggs-directory = ${buildout:directory}/var/eggs 185 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 186 | parts-directory = ${buildout:directory}/var/parts 187 | develop-dir = ${buildout:directory}/var/develop 188 | bin-directory = ${buildout:directory}/bin 189 | requirements = ${buildout:develop}/requirements.txt 190 | 191 | [slartibartfast] 192 | recipe = zc.recipe.egg 193 | eggs = ${buildout:requirements-eggs} 194 | slartibartfast 195 | 196 | ``` 197 | 198 | N.B.: the show command is equivalent to `--output -`. 199 | 200 | # Configure the path 201 | 202 | For your project, there are three important path to configure: 203 | 204 | * where your project root is, 205 | * where your sources are (within your project), 206 | * where your environment will be. 207 | 208 | When you're using buildstrap on a project, the default are safe, as long as you're 209 | running while you're doing it within the sources of the project. Then what you'll have 210 | is: 211 | 212 | * `root_path` → '.' 213 | * `src_path` → `{root_path}` → '.' 214 | * `env_path` → `{root_path}/var` → './var' 215 | * `bin_path` → `{root_path}/bin` → './bin' 216 | 217 | But sometimes, you want to change the defaults, for the best (or the worst 218 | — most often, the worst, though). 219 | 220 | So, you can set all those paths to values other than the default, and have it 221 | all in a very different setup than the default. 222 | 223 | ## Root path: `--root` 224 | 225 | The project's root is where typically all other paths are being relative to. 226 | It's where you'll expect to find the `buildout.cfg` file, and where the 227 | environment directory will be. 228 | 229 | When passed, it's setting up the `directory` directive of the `buildout.cfg` 230 | file, otherwise it's keeping the default. 231 | 232 | ``` 233 | % buildstrap -r /tmp/buildstrap-env/ show buildstrap requirements.txt 234 | [buildout] 235 | newest = false 236 | parts = buildstrap 237 | package = buildstrap 238 | extensions = gp.vcsdevelop 239 | directory = /tmp/buildstrap-env/ 240 | develop = . 241 | eggs-directory = ${buildout:directory}/var/eggs 242 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 243 | parts-directory = ${buildout:directory}/var/parts 244 | develop-dir = ${buildout:directory}/var/develop 245 | bin-directory = ${buildout:directory}/bin 246 | requirements = ${buildout:develop}/requirements.txt 247 | 248 | [buildstrap] 249 | recipe = zc.recipe.egg 250 | eggs = ${buildout:requirements-eggs} 251 | buildstrap 252 | ``` 253 | 254 | ## Sources path: `--src` 255 | 256 | Though, if you change the root directory, chances are (like in the former example) that 257 | it won't be where your sources are. Then, running `buildout` will end up in throwing an 258 | exception: 259 | 260 | FileNotFoundError: [Errno 2] No such file or directory: '/tmp/builstrap-env/./setup.py' 261 | 262 | The source path is where you'll have your `setup.py` file that defines your project. 263 | So, if your `setup.py` is not at the root of your project, you definitely want to 264 | use the `--src` argument. 265 | 266 | ``` 267 | % buildstrap -r /tmp/buildstrap-build -s `pwd`/buildstrap show buildstrap requirements.txt 268 | [buildout] 269 | newest = false 270 | parts = buildstrap 271 | package = buildstrap 272 | extensions = gp.vcsdevelop 273 | directory = /tmp 274 | develop = /absolute/path/to/buildstrap 275 | eggs-directory = ${buildout:directory}/var/eggs 276 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 277 | parts-directory = ${buildout:directory}/var/parts 278 | develop-dir = ${buildout:directory}/var/develop 279 | bin-directory = ${buildout:directory}/bin 280 | requirements = ${buildout:develop}/requirements.txt 281 | 282 | [buildstrap] 283 | recipe = zc.recipe.egg 284 | eggs = ${buildout:requirements-eggs} 285 | buildstrap 286 | 287 | ``` 288 | 289 | *Nota Bene*: if you do not want to use a path relative to the `root` path, then 290 | use an absolute path, or you'll have surprises! As you can see in the example above 291 | the path is made absolute by using the `pwd` command. 292 | 293 | So running this command with buildout will do: 294 | 295 | ``` 296 | % buildout 297 | Creating directory '/tmp/buildstrap-build/var/eggs'. 298 | Getting distribution for 'gp.vcsdevelop'. 299 | warning: no previously-included files matching '*' found under directory 'docs/_build' 300 | Got gp.vcsdevelop 2.2.3. 301 | Creating directory '/tmp/buildstrap-build/bin'. 302 | Creating directory '/tmp/buildstrap-build/var/parts'. 303 | Creating directory '/tmp/buildstrap-build/var/develop-eggs'. 304 | Develop: '/home/guyzmo/Workspace/Projects/buildstrap' 305 | Getting distribution for 'zc.recipe.egg>=2.0.0a3'. 306 | Got zc.recipe.egg 2.0.3. 307 | Unused options for buildout: 'package'. 308 | Installing buildstrap. 309 | Generated script '/tmp/buildstrap-build/bin/buildout'. 310 | Generated script '/tmp/buildstrap-build/bin/buildstrap'. 311 | ``` 312 | 313 | ## Environment path: `--env` 314 | 315 | As seen in the previous example, the script is generating a bunch of directories used 316 | for setting up the environment in `{root_path}/var/`. You might want them to be named 317 | differently, so they're not seen in listings for example: 318 | 319 | ``` 320 | % buildstrap -r /tmp -s `pwd`/buildstrap -e .var show buildstrap requirements.txt 321 | [buildout] 322 | newest = false 323 | parts = buildstrap 324 | package = buildstrap 325 | extensions = gp.vcsdevelop 326 | directory = /tmp 327 | develop = /home/guyzmo/Workspace/Projects/buildstrap/buildstrap 328 | eggs-directory = ${buildout:directory}/.var/eggs 329 | develop-eggs-directory = ${buildout:directory}/.var/develop-eggs 330 | parts-directory = ${buildout:directory}/.var/parts 331 | develop-dir = ${buildout:directory}/.var/develop 332 | bin-directory = ${buildout:directory}/bin 333 | requirements = ${buildout:develop}/requirements.txt 334 | 335 | [buildstrap] 336 | recipe = zc.recipe.egg 337 | eggs = ${buildout:requirements-eggs} 338 | buildstrap 339 | 340 | ``` 341 | 342 | or you might want to put it at any other place, by using an absolute path: 343 | 344 | ``` 345 | % buildstrap -r /tmp -s `pwd`/buildstrap -e /tmp/buildstrap-var show buildstrap requirements.txt 346 | [buildout] 347 | directory = /tmp 348 | develop = /home/guyzmo/Workspace/Projects/buildstrap/buildstrap 349 | eggs-directory = /tmp/buildstrap-var/eggs 350 | develop-eggs-directory = /tmp/buildstrap-var/develop-eggs 351 | parts-directory = /tmp/buildstrap-var/parts 352 | develop-dir = /tmp/buildstrap-var/develop 353 | bin-directory = ${buildout:directory}/bin 354 | … 355 | ``` 356 | 357 | ## Bin path: `--bin` 358 | 359 | Finally, you might not like the default of having the `bin` directory at the 360 | `root` path position, so you can put it within var the following way: 361 | 362 | ``` 363 | % buildstrap -b var/bin show buildstrap requirements.txt 364 | [buildout] 365 | develop = . 366 | eggs-directory = ${buildout:directory}/var/eggs 367 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 368 | parts-directory = ${buildout:directory}/var/parts 369 | develop-dir = ${buildout:directory}/var/develop 370 | bin-directory = ${buildout:directory}/var/bin 371 | … 372 | ``` 373 | 374 | or same as before, to somewhere other place non relative to the sources: 375 | 376 | ``` 377 | % buildstrap -r /tmp -s `pwd`/buildstrap -e /tmp/buildstrap-var -b /tmp/buildstrap-bin show buildstrap requirements.txt 378 | [buildout] 379 | develop = . 380 | directory = /tmp/buildstrap-env/ 381 | eggs-directory = /tmp/buildstrap-var/eggs 382 | develop-eggs-directory = /tmp/buildstrap-var/develop-eggs 383 | parts-directory = /tmp/buildstrap-var/parts 384 | develop-dir = /tmp/buildstrap-var/develop 385 | bin-directory = /tmp/buildstrap-bin 386 | … 387 | ``` 388 | 389 | -------------------------------------------------------------------------------- /doc/buildout.md: -------------------------------------------------------------------------------- 1 | # Buildout configuration 2 | 3 | Before going into more details, let's have briefly a look at a `buildout.cfg` 4 | configuration. Each section of the configuration file are called `part`s in 5 | buildout slang. The part configures a directive to run using a recipe. There 6 | are [many recipes you can lookup][recipes], but in a `buildout.cfg` freshly 7 | baked by buildstrap, you'll only see two: 8 | 9 | * `zc.recipe.egg`: which takes care of downloading and installing dependencies into 10 | the self-contained environment ; 11 | * `gp.vcsdevelop`: which parses a `requirements.txt` file and exposes the 12 | dependencies, so `zc.recipe.egg` can do its job (in the context of buildstrap). 13 | 14 | Then, to setup the environment, there's a section named `[buildout]` that 15 | contains everything needed to setup the self contained environment, like the 16 | list of parts to run (remove one from there and it'll be ignored), the paths to 17 | the used directories… 18 | 19 | Once the buildout configuration file, `buildout.cfg` has been generated, you can tweak 20 | it as much as you like to suit your needs. Buildout is much more than just setting up 21 | a self contained environment! 22 | 23 | If you want to read more about buildout, [check its documentation][doc], or for more 24 | in depth info, [check `buildout.cfg` manual][doc-conf]. 25 | 26 | [recipes]:http://www.buildout.org/en/latest/docs/recipelist.html 27 | [doc]:http://www.buildout.org/en/latest/docs/ 28 | [doc-conf]:https://github.com/buildout/buildout/blob/master/src/zc/buildout/buildout.txt 29 | 30 | -------------------------------------------------------------------------------- /doc/buildstrap.rst: -------------------------------------------------------------------------------- 1 | buildstrap package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | buildstrap.buildstrap module 8 | ---------------------------- 9 | 10 | .. automodule:: buildstrap.buildstrap 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: buildstrap 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # buildstrap documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jun 14 13:14:26 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | import os 24 | 25 | from recommonmark.parser import CommonMarkParser 26 | from recommonmark.transform import AutoStructify 27 | 28 | github_doc_root = 'https://github.com/guyzmo/buildstrap/tree/master/doc/' 29 | 30 | def setup(app): 31 | app.add_config_value('recommonmark_config', { 32 | 'url_resolver': lambda url: github_doc_root + url, 33 | 'auto_toc_tree_section': 'Contents', 34 | 'enable_auto_doc_ref': True, 35 | 'enable_eval_rst': True, 36 | 37 | }, True) 38 | app.add_transform(AutoStructify) 39 | 40 | source_parsers = { '.md': CommonMarkParser } 41 | 42 | # -- General configuration ------------------------------------------------ 43 | 44 | # If your documentation needs a minimal Sphinx version, state it here. 45 | # 46 | # needs_sphinx = '1.0' 47 | 48 | # Add any Sphinx extension module names here, as strings. They can be 49 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 50 | # ones. 51 | extensions = [ 52 | 'sphinx.ext.autodoc', 53 | 'sphinx.ext.viewcode', 54 | 'sphinxcontrib.napoleon', 55 | ] 56 | 57 | # Add any paths that contain templates here, relative to this directory. 58 | templates_path = ['_templates'] 59 | 60 | # The suffix of source filenames. 61 | 62 | # The suffix(es) of source filenames. 63 | # You can specify multiple suffix as a list of string: 64 | # 65 | # source_suffix = '.rst' 66 | source_suffix = [ '.md', '.rst' ] 67 | 68 | # The encoding of source files. 69 | # 70 | # source_encoding = 'utf-8-sig' 71 | 72 | # The master toctree document. 73 | master_doc = 'index' 74 | 75 | # General information about the project. 76 | project = 'buildstrap' 77 | copyright = '2016, Bernard `Guyzmo` Pratz' 78 | author = 'Bernard `Guyzmo` Pratz' 79 | 80 | # The version info for the project you're documenting, acts as replacement for 81 | # |version| and |release|, also used in various other places throughout the 82 | # built documents. 83 | # 84 | # The full version, including alpha/beta/rc tags. 85 | release = open(os.path.abspath('../VERSION'), 'r').read().strip() 86 | 87 | # The short X.Y version. 88 | version = '.'.join(release.split('.')[:1]) 89 | 90 | # The language for content autogenerated by Sphinx. Refer to documentation 91 | # for a list of supported languages. 92 | # 93 | # This is also used if you do content translation via gettext catalogs. 94 | # Usually you set "language" from the command line for these cases. 95 | language = None 96 | 97 | # There are two options for replacing |today|: either, you set today to some 98 | # non-false value, then it is used: 99 | # 100 | # today = '' 101 | # 102 | # Else, today_fmt is used as the format for a strftime call. 103 | # 104 | # today_fmt = '%B %d, %Y' 105 | 106 | # List of patterns, relative to source directory, that match files and 107 | # directories to ignore when looking for source files. 108 | # This patterns also effect to html_static_path and html_extra_path 109 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 110 | 111 | # The reST default role (used for this markup: `text`) to use for all 112 | # documents. 113 | # 114 | # default_role = None 115 | 116 | # If true, '()' will be appended to :func: etc. cross-reference text. 117 | # 118 | # add_function_parentheses = True 119 | 120 | # If true, the current module name will be prepended to all description 121 | # unit titles (such as .. function::). 122 | # 123 | # add_module_names = True 124 | 125 | # If true, sectionauthor and moduleauthor directives will be shown in the 126 | # output. They are ignored by default. 127 | # 128 | # show_authors = False 129 | 130 | # The name of the Pygments (syntax highlighting) style to use. 131 | pygments_style = 'sphinx' 132 | 133 | # A list of ignored prefixes for module index sorting. 134 | # modindex_common_prefix = [] 135 | 136 | # If true, keep warnings as "system message" paragraphs in the built documents. 137 | # keep_warnings = False 138 | 139 | # If true, `todo` and `todoList` produce output, else they produce nothing. 140 | todo_include_todos = False 141 | 142 | 143 | # -- Options for HTML output ---------------------------------------------- 144 | 145 | # The theme to use for HTML and HTML Help pages. See the documentation for 146 | # a list of builtin themes. 147 | # 148 | html_theme = 'sphinx_rtd_theme' 149 | 150 | # Theme options are theme-specific and customize the look and feel of a theme 151 | # further. For a list of options available for each theme, see the 152 | # documentation. 153 | # 154 | # html_theme_options = {} 155 | 156 | # Add any paths that contain custom themes here, relative to this directory. 157 | # html_theme_path = [] 158 | 159 | # The name for this set of Sphinx documents. 160 | # " v documentation" by default. 161 | # 162 | # html_title = 'buildstrap v0' 163 | 164 | # A shorter title for the navigation bar. Default is the same as html_title. 165 | # 166 | # html_short_title = None 167 | 168 | # The name of an image file (relative to this directory) to place at the top 169 | # of the sidebar. 170 | # 171 | # html_logo = None 172 | 173 | # The name of an image file (relative to this directory) to use as a favicon of 174 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 175 | # pixels large. 176 | # 177 | # html_favicon = None 178 | 179 | # Add any paths that contain custom static files (such as style sheets) here, 180 | # relative to this directory. They are copied after the builtin static files, 181 | # so a file named "default.css" will overwrite the builtin "default.css". 182 | html_static_path = ['_static'] 183 | 184 | # Add any extra paths that contain custom files (such as robots.txt or 185 | # .htaccess) here, relative to this directory. These files are copied 186 | # directly to the root of the documentation. 187 | # 188 | # html_extra_path = [] 189 | 190 | # If not None, a 'Last updated on:' timestamp is inserted at every page 191 | # bottom, using the given strftime format. 192 | # The empty string is equivalent to '%b %d, %Y'. 193 | # 194 | # html_last_updated_fmt = None 195 | 196 | # If true, SmartyPants will be used to convert quotes and dashes to 197 | # typographically correct entities. 198 | # 199 | # html_use_smartypants = True 200 | 201 | # Custom sidebar templates, maps document names to template names. 202 | # 203 | # html_sidebars = {} 204 | 205 | # Additional templates that should be rendered to pages, maps page names to 206 | # template names. 207 | # 208 | # html_additional_pages = {} 209 | 210 | # If false, no module index is generated. 211 | # 212 | # html_domain_indices = True 213 | 214 | # If false, no index is generated. 215 | # 216 | # html_use_index = True 217 | 218 | # If true, the index is split into individual pages for each letter. 219 | # 220 | # html_split_index = False 221 | 222 | # If true, links to the reST sources are added to the pages. 223 | # 224 | # html_show_sourcelink = True 225 | 226 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 227 | # 228 | # html_show_sphinx = True 229 | 230 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 231 | # 232 | # html_show_copyright = True 233 | 234 | # If true, an OpenSearch description file will be output, and all pages will 235 | # contain a tag referring to it. The value of this option must be the 236 | # base URL from which the finished HTML is served. 237 | # 238 | # html_use_opensearch = '' 239 | 240 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 241 | # html_file_suffix = None 242 | 243 | # Language to be used for generating the HTML full-text search index. 244 | # Sphinx supports the following languages: 245 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 246 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 247 | # 248 | # html_search_language = 'en' 249 | 250 | # A dictionary with options for the search language support, empty by default. 251 | # 'ja' uses this config value. 252 | # 'zh' user can custom change `jieba` dictionary path. 253 | # 254 | # html_search_options = {'type': 'default'} 255 | 256 | # The name of a javascript file (relative to the configuration directory) that 257 | # implements a search results scorer. If empty, the default will be used. 258 | # 259 | # html_search_scorer = 'scorer.js' 260 | 261 | # Output file base name for HTML help builder. 262 | htmlhelp_basename = 'buildstrapdoc' 263 | 264 | # -- Options for LaTeX output --------------------------------------------- 265 | 266 | latex_elements = { 267 | # The paper size ('letterpaper' or 'a4paper'). 268 | # 269 | # 'papersize': 'letterpaper', 270 | 271 | # The font size ('10pt', '11pt' or '12pt'). 272 | # 273 | # 'pointsize': '10pt', 274 | 275 | # Additional stuff for the LaTeX preamble. 276 | # 277 | # 'preamble': '', 278 | 279 | # Latex figure (float) alignment 280 | # 281 | # 'figure_align': 'htbp', 282 | } 283 | 284 | # Grouping the document tree into LaTeX files. List of tuples 285 | # (source start file, target name, title, 286 | # author, documentclass [howto, manual, or own class]). 287 | latex_documents = [ 288 | (master_doc, 'buildstrap.tex', 'buildstrap Documentation', 289 | 'Bernard {}`Guyzmo{}` Pratz', 'manual'), 290 | ] 291 | 292 | # The name of an image file (relative to this directory) to place at the top of 293 | # the title page. 294 | # 295 | # latex_logo = None 296 | 297 | # For "manual" documents, if this is true, then toplevel headings are parts, 298 | # not chapters. 299 | # 300 | # latex_use_parts = False 301 | 302 | # If true, show page references after internal links. 303 | # 304 | # latex_show_pagerefs = False 305 | 306 | # If true, show URL addresses after external links. 307 | # 308 | # latex_show_urls = False 309 | 310 | # Documents to append as an appendix to all manuals. 311 | # 312 | # latex_appendices = [] 313 | 314 | # If false, no module index is generated. 315 | # 316 | # latex_domain_indices = True 317 | 318 | 319 | # -- Options for manual page output --------------------------------------- 320 | 321 | # One entry per manual page. List of tuples 322 | # (source start file, name, description, authors, manual section). 323 | man_pages = [ 324 | (master_doc, 'buildstrap', 'buildstrap Documentation', 325 | [author], 1) 326 | ] 327 | 328 | # If true, show URL addresses after external links. 329 | # 330 | # man_show_urls = False 331 | 332 | 333 | # -- Options for Texinfo output ------------------------------------------- 334 | 335 | # Grouping the document tree into Texinfo files. List of tuples 336 | # (source start file, target name, title, author, 337 | # dir menu entry, description, category) 338 | texinfo_documents = [ 339 | (master_doc, 'buildstrap', 'buildstrap Documentation', 340 | author, 'buildstrap', 'One line description of project.', 341 | 'Miscellaneous'), 342 | ] 343 | 344 | # Documents to append as an appendix to all manuals. 345 | # 346 | # texinfo_appendices = [] 347 | 348 | # If false, no module index is generated. 349 | # 350 | # texinfo_domain_indices = True 351 | 352 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 353 | # 354 | # texinfo_show_urls = 'footnote' 355 | 356 | # If true, do not generate a @detailmenu in the "Top" node's menu. 357 | # 358 | # texinfo_no_detailmenu = False 359 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Buildstrap: self contained env with ♥ 2 | 3 | For those who want to hack on any project without having to hack around your 4 | shell environment, mess with your python tools, pollute your home dotfiles, 5 | buildstrap is for you. 6 | 7 | This project will bring the power of [buildout], by generating in a simple 8 | command all you need to setup a buildout configuration, that will then create 9 | a self contained python environment for all your hacking needs. 10 | 11 | It's as simple as: 12 | 13 | ``` 14 | % git clone https://github.com/guyzmo/buildstrap 15 | % cd buildstrap 16 | % buildstrap run buildstrap requirements.txt 17 | … 18 | % bin/buildstrap --version 19 | 0.1.1 20 | ``` 21 | 22 | What is being done here, is that you tell buildstrap the package's name, 23 | and the requirements files to parse, and it will generate the the following 24 | `buildout.cfg` file: 25 | 26 | ``` 27 | [buildout] 28 | newest = false 29 | parts = buildstrap 30 | develop = . 31 | eggs-directory = ${buildout:directory}/var/eggs 32 | develop-eggs-directory = ${buildout:directory}/var/develop-eggs 33 | develop-dir = ${buildout:directory}/var/develop 34 | parts-directory = ${buildout:directory}/var/parts 35 | requirements = ${buildout:develop}/requirements.txt 36 | 37 | [buildstrap] 38 | eggs = ${buildout:requirements-eggs} 39 | buildstrap 40 | recipe = zc.recipe.egg 41 | ``` 42 | 43 | That file is then used to configure buildout so it creates the environment 44 | in your project's directory. You'll find all your dependencies downloaded 45 | into `/var`, and all the scripts you need populated in `/bin`. 46 | 47 | So, it's only two directories to add to your `.gitignore`, and to delete 48 | when you want to make your workspace clean again. Then you can choose to 49 | either keep (and eventually tweak) your `buildout.cfg` file, or throw it 50 | away. 51 | 52 | Yes, it's as easy as it sounds! 53 | 54 | [buildout]:https://github.com/buildout/buildout 55 | 56 | ```eval_rst 57 | 58 | .. toctree:: 59 | quickstart 60 | buildout 61 | advanced_usage 62 | buildstrap 63 | 64 | ``` 65 | -------------------------------------------------------------------------------- /doc/modules.rst: -------------------------------------------------------------------------------- 1 | buildstrap 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | buildstrap 8 | -------------------------------------------------------------------------------- /doc/quickstart.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /requirements-doc.txt: -------------------------------------------------------------------------------- 1 | recommonmark==0.4.0 2 | Sphinx==1.4.4 3 | sphinx-rtd-theme==0.1.9 4 | sphinxcontrib-napoleon==0.5.1 5 | -r requirements.txt 6 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | pytest-flakes 4 | pytest-pep8 5 | pytest-xdist 6 | pytest-sugar 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zc.buildout 2 | docopt 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import sys 4 | 5 | if sys.version_info.major < 3: 6 | print('Please install with python version 3') 7 | sys.exit(1) 8 | 9 | from distutils.core import Command 10 | from distutils.core import setup 11 | from setuptools.command.test import test as TestCommand 12 | 13 | class PyTest(TestCommand): 14 | user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] 15 | 16 | def initialize_options(self): 17 | TestCommand.initialize_options(self) 18 | self.pytest_args = [] 19 | 20 | def finalize_options(self): 21 | TestCommand.finalize_options(self) 22 | self.test_args = [] 23 | self.test_suite = True 24 | 25 | def run_tests(self): 26 | import pytest 27 | 28 | if len(self.pytest_args) == 0: 29 | self.pytest_args = ['--cov=buildstrap', '--cov-report=term-missing'] 30 | errno = pytest.main(self.pytest_args) 31 | sys.exit(errno) 32 | 33 | 34 | class dist_clean(Command): 35 | description = 'Clean the repository from all buildout stuff' 36 | user_options = [] 37 | 38 | def initialize_options(self): 39 | pass 40 | 41 | def finalize_options(self): 42 | pass 43 | 44 | 45 | def run(self): 46 | import shutil, os 47 | from glob import glob 48 | path = os.path.split(__file__)[0] 49 | shutil.rmtree(os.path.join(path, 'var'), ignore_errors=True) 50 | shutil.rmtree(os.path.join(path, 'bin'), ignore_errors=True) 51 | shutil.rmtree(os.path.join(path, '.tox'), ignore_errors=True) 52 | for fname in glob('*.egg-info'): 53 | shutil.rmtree(os.path.join(path, fname), ignore_errors=True) 54 | shutil.rmtree(os.path.join(path, '.eggs'), ignore_errors=True) 55 | shutil.rmtree(os.path.join(path, 'build'), ignore_errors=True) 56 | shutil.rmtree(os.path.join(path, 'dist'), ignore_errors=True) 57 | shutil.rmtree(os.path.join(path, '.installed.cfg'), ignore_errors=True) 58 | print("Repository is now clean!") 59 | 60 | version = open('VERSION', 'r').read().strip() 61 | 62 | setup(name='buildstrap', 63 | version=version, 64 | description='Tool for managing remote repositories from your git CLI!', 65 | classifiers=[ 66 | # 'Development Status :: 2 - Pre-Alpha', 67 | # 'Development Status :: 3 - Alpha', 68 | 'Development Status :: 4 - Beta', 69 | # 'Development Status :: 5 - Production/Stable', 70 | # 'Development Status :: 6 - Mature', 71 | # 'Development Status :: 7 - Inactive', 72 | 'Programming Language :: Python :: 3', 73 | 'Programming Language :: Python :: 3.4', 74 | 'Programming Language :: Python :: 3.5', 75 | 'Framework :: Buildout', 76 | 'Environment :: Console', 77 | 'Intended Audience :: Developers', 78 | 'License :: Freely Distributable', 79 | 'Topic :: Software Development', 80 | 'Topic :: Software Development :: Build Tools', 81 | 'Topic :: Utilities', 82 | ], 83 | keywords='git', 84 | url='https://github.com/guyzmo/buildstrap', 85 | author='Bernard `Guyzmo` Pratz', 86 | author_email='guyzmo+buildstrap+pub@m0g.net', 87 | setup_requires=[ 88 | 'setuptools-markdown' 89 | ], 90 | long_description_markdown_filename='README.md', 91 | include_package_data = True, 92 | install_requires=[ 93 | 'docopt', 94 | 'zc.buildout', 95 | ], 96 | entry_points=""" 97 | # -*- Entry points: -*- 98 | [console_scripts] 99 | buildstrap = buildstrap.buildstrap:run 100 | """, 101 | license='WTFPL', 102 | packages=find_packages(exclude=['tests']), 103 | cmdclass={ 104 | 'test': PyTest, 105 | 'dist_clean': dist_clean 106 | }, 107 | tests_require=[ 108 | 'pytest', 109 | 'pytest-cov', 110 | 'pytest-sugar', 111 | 'pytest-catchlog', 112 | 'pytest-datadir-ng', 113 | ], 114 | zip_safe=False 115 | ) 116 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guyzmo/buildstrap/d922cbff2df808086d0792ed356ec146f4531bab/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_buildstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #from tempfile import TemporaryDirectory 4 | 5 | #from testfixtures import Replace, ShouldRaise, compare 6 | #from testfixtures.popen import MockPopen 7 | 8 | import pytest 9 | 10 | from buildstrap.buildstrap import * 11 | 12 | from pprint import pprint 13 | 14 | def test_list(): 15 | l_nil = [] 16 | l_one = ['a'] 17 | l_two = ['a', 'b'] 18 | l_thr = ['a', 'b', 'c'] 19 | 20 | lb_nil = ListBuildout([]) 21 | lb_one = ListBuildout(['a']) 22 | lb_two = ListBuildout(['a', 'b']) 23 | lb_thr = ListBuildout(['a', 'b', 'c']) 24 | 25 | assert str(lb_nil) == str(l_nil) 26 | assert str(lb_one) == str(l_one) 27 | assert str(lb_two) == str(l_two) 28 | assert str(lb_thr) == str(l_thr) 29 | 30 | with ListBuildout.generate_context(): 31 | assert str(lb_nil) != str(l_nil) 32 | assert str(lb_one) != str(l_one) 33 | assert str(lb_two) != str(l_two) 34 | assert str(lb_thr) != str(l_thr) 35 | 36 | assert str(lb_nil) == '\n'.join(l_nil) 37 | assert str(lb_one) == '\n'.join(l_one) 38 | assert str(lb_two) == '\n'.join(l_two) 39 | assert str(lb_thr) == '\n'.join(l_thr) 40 | 41 | 42 | class TestFun__build_part_target: 43 | def test_build_part_target__target(self): 44 | assert build_part_target('a') == { 45 | 'a': OrderedDict([ 46 | ('recipe', 'zc.recipe.egg'), 47 | ('eggs', ['${buildout:requirements-eggs}']) 48 | ]) 49 | } 50 | 51 | def test_build_part_target__target_one_pkg(self): 52 | assert build_part_target('a', ['b']) == { 53 | 'a': OrderedDict([ 54 | ('recipe', 'zc.recipe.egg'), 55 | ('eggs', ['${buildout:requirements-eggs}', 'b']) 56 | ]) 57 | } 58 | 59 | def test_build_part_target__target_mult_pkg(self): 60 | assert build_part_target('a', ['b1', 'b2', 'b3', 'b4']) == { 61 | 'a': OrderedDict([ 62 | ('recipe', 'zc.recipe.egg'), 63 | ('eggs', ['${buildout:requirements-eggs}', 'b1', 'b2', 'b3', 'b4']), 64 | ]) 65 | } 66 | 67 | def test_build_part_target__target_inter(self): 68 | assert build_part_target('a', interpreter='c') == { 69 | 'a': OrderedDict([ 70 | ('recipe', 'zc.recipe.egg'), 71 | ('eggs', ['${buildout:requirements-eggs}']), 72 | ('interpreter', 'c') 73 | ]) 74 | } 75 | 76 | def test_build_part_target__target_one_pkg_inter(self): 77 | assert build_part_target('a', ['b'], 'c') == { 78 | 'a': OrderedDict([ 79 | ('recipe', 'zc.recipe.egg'), 80 | ('eggs', ['${buildout:requirements-eggs}', 'b']), 81 | ('interpreter', 'c') 82 | ]) 83 | } 84 | 85 | def test_build_part_target__target_mult_pkg_inter(self): 86 | assert build_part_target('a', ['b1', 'b2', 'b3', 'b4'], 'c') == { 87 | 'a': OrderedDict([ 88 | ('recipe', 'zc.recipe.egg'), 89 | ('eggs', ['${buildout:requirements-eggs}', 'b1', 'b2', 'b3', 'b4']), 90 | ('interpreter', 'c') 91 | ]) 92 | } 93 | 94 | ## 95 | 96 | class TestFun__build_part_template: 97 | def test_exists(self): 98 | assert build_part_template('pytest', '') == { 99 | 'pytest': OrderedDict([ 100 | ('arguments', "['--cov={}/{}'.format('${buildout:develop}', " 101 | 'package) for package in ' 102 | "'${buildout:package}'.split(',')] \\\n" 103 | "+['--cov-report', 'term-missing', " 104 | "'tests']+sys.argv[1:]"), 105 | ('eggs', '${buildout:requirements-eggs}'), 106 | ('recipe', 'zc.recipe.egg'), 107 | ]) 108 | } 109 | assert build_part_template('sphinx', '') == { 110 | 'sphinx': OrderedDict([ 111 | ('build', '${buildout:directory}/doc/_build'), 112 | ('eggs', '${buildout:requirements-eggs}'), 113 | ('recipe', 'collective.recipe.sphinxbuilder'), 114 | ('source', '${buildout:directory}/doc'), 115 | ]) 116 | } 117 | 118 | def test_doesnotexists(self): 119 | with pytest.raises(FileNotFoundError): 120 | build_part_template('doesnotexists', '') 121 | 122 | ## 123 | 124 | class TestFun__list_part_template: 125 | def test_list(self): 126 | assert list(list_part_templates('')) == ['pytest', 'sphinx'] 127 | 128 | ## 129 | 130 | class TestFun__build_part_buildout: 131 | def test_build_part_buildout__default_ordering(self): 132 | assert build_part_buildout() == { 133 | 'buildout': OrderedDict([('newest', 'false'), 134 | ('parts', ''), 135 | ('package', ''), 136 | ('extensions', 'gp.vcsdevelop'), 137 | ('develop', '.'), 138 | ('eggs-directory', '${buildout:directory}/var/eggs'), 139 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 140 | ('parts-directory', '${buildout:directory}/var/parts'), 141 | ('develop-dir', '${buildout:directory}/var/develop'), 142 | ('bin-directory', '${buildout:directory}/bin'), 143 | ('requirements', []) 144 | ]) 145 | } 146 | 147 | def test_build_part_buildout__src(self): 148 | buildout_part = build_part_buildout(src_path='bar') 149 | assert buildout_part['buildout']['newest'] == 'false' 150 | assert buildout_part['buildout']['parts'] == '' 151 | assert buildout_part['buildout']['develop'] == 'bar' 152 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 153 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 154 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 155 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 156 | 157 | def test_build_part_buildout__env(self): 158 | buildout_part = build_part_buildout(env_path='bar') 159 | assert buildout_part['buildout']['newest'] == 'false' 160 | assert buildout_part['buildout']['parts'] == '' 161 | assert buildout_part['buildout']['develop'] == '.' 162 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/bar/eggs' 163 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/bar/develop-eggs' 164 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/bar/parts' 165 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 166 | 167 | def test_build_part_buildout__absenv(self): 168 | buildout_part = build_part_buildout(env_path='/bar') 169 | assert buildout_part['buildout']['newest'] == 'false' 170 | assert buildout_part['buildout']['parts'] == '' 171 | assert buildout_part['buildout']['develop'] == '.' 172 | assert buildout_part['buildout']['eggs-directory'] == '/bar/eggs' 173 | assert buildout_part['buildout']['develop-eggs-directory'] == '/bar/develop-eggs' 174 | assert buildout_part['buildout']['parts-directory'] == '/bar/parts' 175 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 176 | 177 | def test_build_part_buildout__bin(self): 178 | buildout_part = build_part_buildout(bin_path='bar') 179 | assert buildout_part['buildout']['newest'] == 'false' 180 | assert buildout_part['buildout']['parts'] == '' 181 | assert buildout_part['buildout']['develop'] == '.' 182 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 183 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 184 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 185 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bar' 186 | 187 | def test_build_part_buildout__absbin(self): 188 | buildout_part = build_part_buildout(bin_path='/bar') 189 | assert buildout_part['buildout']['newest'] == 'false' 190 | assert buildout_part['buildout']['parts'] == '' 191 | assert buildout_part['buildout']['develop'] == '.' 192 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 193 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 194 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 195 | assert buildout_part['buildout']['bin-directory'] == '/bar' 196 | 197 | def test_build_part_buildout__root(self): 198 | buildout_part = build_part_buildout(root_path='/foo') 199 | assert buildout_part['buildout']['newest'] == 'false' 200 | assert buildout_part['buildout']['parts'] == '' 201 | assert buildout_part['buildout']['directory'] == '/foo' 202 | assert buildout_part['buildout']['develop'] == '.' 203 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 204 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 205 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 206 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 207 | 208 | def test_build_part_buildout__root_src(self): 209 | buildout_part = build_part_buildout(root_path='/foo', src_path='bar') 210 | assert buildout_part['buildout']['newest'] == 'false' 211 | assert buildout_part['buildout']['parts'] == '' 212 | assert buildout_part['buildout']['directory'] == '/foo' 213 | assert buildout_part['buildout']['develop'] == 'bar' 214 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 215 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 216 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 217 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 218 | 219 | def test_build_part_buildout__root_env(self): 220 | buildout_part = build_part_buildout(root_path='/foo', env_path='bar') 221 | assert buildout_part['buildout']['newest'] == 'false' 222 | assert buildout_part['buildout']['parts'] == '' 223 | assert buildout_part['buildout']['directory'] == '/foo' 224 | assert buildout_part['buildout']['develop'] == '.' 225 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/bar/eggs' 226 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/bar/develop-eggs' 227 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/bar/parts' 228 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 229 | 230 | def test_build_part_buildout__root_absenv(self): 231 | buildout_part = build_part_buildout(root_path='/foo', env_path='/bar') 232 | assert buildout_part['buildout']['newest'] == 'false' 233 | assert buildout_part['buildout']['parts'] == '' 234 | assert buildout_part['buildout']['directory'] == '/foo' 235 | assert buildout_part['buildout']['develop'] == '.' 236 | assert buildout_part['buildout']['eggs-directory'] == '/bar/eggs' 237 | assert buildout_part['buildout']['develop-eggs-directory'] == '/bar/develop-eggs' 238 | assert buildout_part['buildout']['parts-directory'] == '/bar/parts' 239 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bin' 240 | 241 | def test_build_part_buildout__root_bin(self): 242 | buildout_part = build_part_buildout(root_path='/foo', bin_path='bar') 243 | assert buildout_part['buildout']['newest'] == 'false' 244 | assert buildout_part['buildout']['parts'] == '' 245 | assert buildout_part['buildout']['directory'] == '/foo' 246 | assert buildout_part['buildout']['develop'] == '.' 247 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 248 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 249 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 250 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bar' 251 | 252 | def test_build_part_buildout__root_absbin(self): 253 | buildout_part = build_part_buildout(root_path='/foo', bin_path='/bar') 254 | assert buildout_part['buildout']['newest'] == 'false' 255 | assert buildout_part['buildout']['parts'] == '' 256 | assert buildout_part['buildout']['directory'] == '/foo' 257 | assert buildout_part['buildout']['develop'] == '.' 258 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/var/eggs' 259 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/var/develop-eggs' 260 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/var/parts' 261 | assert buildout_part['buildout']['bin-directory'] == '/bar' 262 | 263 | def test_build_part_buildout__root_env_bin(self): 264 | buildout_part = build_part_buildout(root_path='/foo', env_path='bar', bin_path='bar') 265 | assert buildout_part['buildout']['newest'] == 'false' 266 | assert buildout_part['buildout']['parts'] == '' 267 | assert buildout_part['buildout']['directory'] == '/foo' 268 | assert buildout_part['buildout']['develop'] == '.' 269 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/bar/eggs' 270 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/bar/develop-eggs' 271 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/bar/parts' 272 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/bar' 273 | 274 | def test_build_part_buildout__root_env_absbin(self): 275 | buildout_part = build_part_buildout(root_path='/foo', env_path='bar', bin_path='/bar') 276 | assert buildout_part['buildout']['newest'] == 'false' 277 | assert buildout_part['buildout']['parts'] == '' 278 | assert buildout_part['buildout']['directory'] == '/foo' 279 | assert buildout_part['buildout']['develop'] == '.' 280 | assert buildout_part['buildout']['eggs-directory'] == '${buildout:directory}/bar/eggs' 281 | assert buildout_part['buildout']['develop-eggs-directory'] == '${buildout:directory}/bar/develop-eggs' 282 | assert buildout_part['buildout']['parts-directory'] == '${buildout:directory}/bar/parts' 283 | assert buildout_part['buildout']['bin-directory'] == '/bar' 284 | 285 | def test_build_part_buildout__root_absenv_bin(self): 286 | buildout_part = build_part_buildout(root_path='/foo', env_path='/bar', bin_path='fubar') 287 | assert buildout_part['buildout']['newest'] == 'false' 288 | assert buildout_part['buildout']['parts'] == '' 289 | assert buildout_part['buildout']['directory'] == '/foo' 290 | assert buildout_part['buildout']['develop'] == '.' 291 | assert buildout_part['buildout']['eggs-directory'] == '/bar/eggs' 292 | assert buildout_part['buildout']['develop-eggs-directory'] == '/bar/develop-eggs' 293 | assert buildout_part['buildout']['parts-directory'] == '/bar/parts' 294 | assert buildout_part['buildout']['bin-directory'] == '${buildout:directory}/fubar' 295 | 296 | def test_build_part_buildout__root_absenv_absbin(self): 297 | buildout_part = build_part_buildout(root_path='/foo', env_path='/bar', bin_path='/fubar') 298 | assert buildout_part['buildout']['newest'] == 'false' 299 | assert buildout_part['buildout']['parts'] == '' 300 | assert buildout_part['buildout']['directory'] == '/foo' 301 | assert buildout_part['buildout']['develop'] == '.' 302 | assert buildout_part['buildout']['eggs-directory'] == '/bar/eggs' 303 | assert buildout_part['buildout']['develop-eggs-directory'] == '/bar/develop-eggs' 304 | assert buildout_part['buildout']['parts-directory'] == '/bar/parts' 305 | assert buildout_part['buildout']['bin-directory'] == '/fubar' 306 | 307 | class TestFun_build_parts: 308 | def get_buildout_part(self, packages, parts=[], requirements=[]): 309 | return OrderedDict([ 310 | ('newest', 'false'), 311 | ('parts', parts), 312 | ('package', packages), 313 | ('extensions', 'gp.vcsdevelop'), 314 | ('directory', '.'), 315 | ('develop', '.'), 316 | ('eggs-directory', '${buildout:directory}/var/eggs'), 317 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 318 | ('parts-directory', '${buildout:directory}/var/parts'), 319 | ('develop-dir', '${buildout:directory}/var/develop'), 320 | ('bin-directory', '${buildout:directory}/bin'), 321 | ('requirements', [os.path.join('${buildout:develop}', r) for r in requirements]), 322 | ]) 323 | 324 | def test_build_parts__packages_uniq__req_uniq(self): 325 | parts = build_parts(packages='a', requirements='b') 326 | pprint(parts) 327 | assert parts['buildout'] == self.get_buildout_part('a', parts=['a'], requirements=['b']) 328 | assert parts['a']['recipe'] == 'zc.recipe.egg' 329 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a'] 330 | 331 | def test_build_parts__packages_uniq__req_list(self): 332 | parts = build_parts(packages='a', requirements=['b', 'c', 'd']) 333 | pprint(parts) 334 | assert parts['buildout'] == self.get_buildout_part('a', ['a'], ['b', 'c', 'd']) 335 | assert parts['a']['recipe'] == 'zc.recipe.egg' 336 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a'] 337 | 338 | def test_build_parts__packages_uniq__req_lstr(self): 339 | parts = build_parts(packages='a', requirements='b,c,d') 340 | pprint(parts) 341 | assert parts['buildout'] == self.get_buildout_part('a', ['a'], ['b', 'c', 'd']) 342 | assert parts['a']['recipe'] == 'zc.recipe.egg' 343 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a'] 344 | 345 | def test_build_parts__packages_list__req_uniq(self): 346 | parts = build_parts(packages=['a', 'x'], requirements='b') 347 | pprint(parts) 348 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b']) 349 | assert parts['a']['recipe'] == 'zc.recipe.egg' 350 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 351 | 352 | def test_build_parts__packages_list__req_list(self): 353 | parts = build_parts(packages=['a', 'x'], requirements=['b', 'c', 'd']) 354 | pprint(parts) 355 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b', 'c', 'd']) 356 | assert parts['a']['recipe'] == 'zc.recipe.egg' 357 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 358 | 359 | def test_build_parts__packages_list__req_lstr(self): 360 | parts = build_parts(packages=['a', 'x'], requirements='b,c,d') 361 | pprint(parts) 362 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b', 'c', 'd']) 363 | assert parts['a']['recipe'] == 'zc.recipe.egg' 364 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 365 | 366 | def test_build_parts__packages_list__req_lstr(self): 367 | parts = build_parts(packages=['a', 'x'], requirements='b,c,d') 368 | pprint(parts) 369 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b', 'c', 'd']) 370 | assert parts['a']['recipe'] == 'zc.recipe.egg' 371 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 372 | 373 | def test_build_parts__packages_lstr__req_uniq(self): 374 | parts = build_parts(packages='a,x', requirements='b') 375 | pprint(parts) 376 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b']) 377 | assert parts['a']['recipe'] == 'zc.recipe.egg' 378 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 379 | 380 | def test_build_parts__packages_lstr__req_list(self): 381 | parts = build_parts(packages='a,x', requirements=['b', 'c', 'd']) 382 | pprint(parts) 383 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b', 'c', 'd']) 384 | assert parts['a']['recipe'] == 'zc.recipe.egg' 385 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 386 | 387 | def test_build_parts__packages_lstr__req_lstr(self): 388 | parts = build_parts(packages='a,x', requirements='b,c,d') 389 | pprint(parts) 390 | assert parts['buildout'] == self.get_buildout_part('a x', ['a'], ['b', 'c', 'd']) 391 | assert parts['a']['recipe'] == 'zc.recipe.egg' 392 | assert parts['a']['eggs'] == ['${buildout:requirements-eggs}', 'a', 'x'] 393 | 394 | from contextlib import contextmanager 395 | 396 | class UnitConfig: 397 | def __init__(self, args, internal, output): 398 | self.args = args 399 | self.internal = internal 400 | self.output = output 401 | 402 | unit_config_list = { 403 | 'config_show_buildstrap': UnitConfig( 404 | args={'--bin': 'bin', 405 | '--config': '~/.config/buildstrap', 406 | '--env': 'var', 407 | '--force': True, 408 | '--help': False, 409 | '--interpreter': 'python3', 410 | '--output': 'irrelevant', 411 | '--part': ['pytest', 'sphinx'], 412 | '--root': None, 413 | '--src': None, 414 | '--verbose': 0, 415 | '--version': False, 416 | '': 'buildstrap', 417 | '': ['requirements.txt', 'requirements-doc.txt', 'requirements-test.txt'], 418 | 'debug': False, 419 | 'generate': False, 420 | 'run': False, 421 | 'show': True}, 422 | internal=OrderedDict([('buildout', 423 | OrderedDict([('newest', 'false'), 424 | ('parts', ListBuildout(['buildstrap', 'pytest', 'sphinx'])), 425 | ('package', 'buildstrap'), 426 | ('extensions', 'gp.vcsdevelop'), 427 | ('develop', '.'), 428 | ('eggs-directory', '${buildout:directory}/var/eggs'), 429 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 430 | ('parts-directory', '${buildout:directory}/var/parts'), 431 | ('develop-dir', '${buildout:directory}/var/develop'), 432 | ('bin-directory', '${buildout:directory}/bin'), 433 | ('requirements', ListBuildout(['${buildout:develop}/requirements.txt', 434 | '${buildout:develop}/requirements-doc.txt', '${buildout:develop}/requirements-test.txt'])), 435 | ])), 436 | ('buildstrap', 437 | OrderedDict([('recipe', 'zc.recipe.egg'), 438 | ('eggs', ListBuildout(['${buildout:requirements-eggs}', 'buildstrap'])), 439 | ('interpreter', 'python3')])), 440 | ('pytest', 441 | OrderedDict([('recipe', 'zc.recipe.egg'), 442 | ('eggs', ListBuildout(['${buildout:requirements-eggs}'])), 443 | ('interpreter', 'python3')])), 444 | ('pytest', 445 | OrderedDict([ 446 | ('arguments', "['--cov={}/{}'.format('${buildout:develop}', " 447 | 'package) for package in ' 448 | "'${buildout:package}'.split(',')] \\\n" 449 | "+['--cov-report', 'term-missing', " 450 | "'tests']+sys.argv[1:]"), 451 | ('eggs', ListBuildout(['${buildout:requirements-eggs}'])), 452 | ('recipe', 'zc.recipe.egg'), 453 | ])), 454 | ('sphinx', 455 | OrderedDict([ 456 | ('build', '${buildout:directory}/doc/_build'), 457 | ('eggs', ListBuildout(['${buildout:requirements-eggs}'])), 458 | ('recipe', 'collective.recipe.sphinxbuilder'), 459 | ('source', '${buildout:directory}/doc'), 460 | ])), 461 | ]), 462 | output="\n".join([ 463 | '[buildout]', 464 | 'newest = false', 465 | 'parts = buildstrap', 466 | ' pytest', 467 | ' sphinx', 468 | 'package = buildstrap', 469 | 'extensions = gp.vcsdevelop', 470 | 'develop = .', 471 | 'eggs-directory = ${buildout:directory}/var/eggs', 472 | 'develop-eggs-directory = ${buildout:directory}/var/develop-eggs', 473 | 'parts-directory = ${buildout:directory}/var/parts', 474 | 'develop-dir = ${buildout:directory}/var/develop', 475 | 'bin-directory = ${buildout:directory}/bin', 476 | 'requirements = ${buildout:develop}/requirements.txt', 477 | ' ${buildout:develop}/requirements-doc.txt', 478 | ' ${buildout:develop}/requirements-test.txt', 479 | '', 480 | '[buildstrap]', 481 | 'recipe = zc.recipe.egg', 482 | 'eggs = ${buildout:requirements-eggs}', 483 | ' buildstrap', 484 | 'interpreter = python3', 485 | '', 486 | '[pytest]', 487 | "arguments = ['--cov={}/{}'.format('${buildout:develop}', package) for package in '${buildout:package}'.split(',')] \\", 488 | " +['--cov-report', 'term-missing', 'tests']+sys.argv[1:]", 489 | 'eggs = ${buildout:requirements-eggs}', 490 | 'recipe = zc.recipe.egg', 491 | '', 492 | '[sphinx]', 493 | 'build = ${buildout:directory}/doc/_build', 494 | 'eggs = ${buildout:requirements-eggs}', 495 | 'recipe = collective.recipe.sphinxbuilder', 496 | 'source = ${buildout:directory}/doc', 497 | '', 498 | '', 499 | ]) 500 | ), 501 | 502 | 'config_show_min': UnitConfig( 503 | args={'--bin': 'bin', 504 | '--config': '~/.config/buildstrap', 505 | '--env': 'var', 506 | '--force': True, 507 | '--help': False, 508 | '--interpreter': None, 509 | '--output': 'irrelevant', 510 | '--part': [], 511 | '--root': None, 512 | '--src': None, 513 | '--verbose': 0, 514 | '--version': False, 515 | '': 'buildstrap', 516 | '': ['requirements.txt'], 517 | 'debug': False, 518 | 'generate': False, 519 | 'run': False, 520 | 'show': True}, 521 | internal=OrderedDict([ 522 | ('buildout', 523 | OrderedDict([('newest', 'false'), 524 | ('parts', ListBuildout(['buildstrap'])), 525 | ('package', 'buildstrap'), 526 | ('extensions', 'gp.vcsdevelop'), 527 | ('develop', '.'), 528 | ('eggs-directory', '${buildout:directory}/var/eggs'), 529 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 530 | ('parts-directory', '${buildout:directory}/var/parts'), 531 | ('develop-dir', '${buildout:directory}/var/develop'), 532 | ('bin-directory', '${buildout:directory}/bin'), 533 | ('requirements', '${buildout:develop}/requirements.txt'), 534 | ])), 535 | ('buildstrap', 536 | OrderedDict([('recipe', 'zc.recipe.egg'), 537 | ('eggs', 538 | ListBuildout(['${buildout:requirements-eggs}', 'buildstrap']))])), 539 | ]), 540 | output='\n'.join(['[buildout]', 541 | 'newest = false', 542 | 'parts = buildstrap', 543 | 'package = buildstrap', 544 | 'extensions = gp.vcsdevelop', 545 | 'develop = .', 546 | 'eggs-directory = ${buildout:directory}/var/eggs', 547 | 'develop-eggs-directory = ${buildout:directory}/var/develop-eggs', 548 | 'parts-directory = ${buildout:directory}/var/parts', 549 | 'develop-dir = ${buildout:directory}/var/develop', 550 | 'bin-directory = ${buildout:directory}/bin', 551 | 'requirements = ${buildout:develop}/requirements.txt', 552 | '', 553 | '[buildstrap]', 554 | 'recipe = zc.recipe.egg', 555 | 'eggs = ${buildout:requirements-eggs}', 556 | ' buildstrap', 557 | '', 558 | '', 559 | ]) 560 | ), 561 | 562 | 'config_show_min_path': UnitConfig( 563 | args={'--bin': '/bin', 564 | '--config': '~/.config/buildstrap', 565 | '--env': '/env', 566 | '--force': True, 567 | '--help': False, 568 | '--interpreter': None, 569 | '--output': 'irrelevant', 570 | '--part': [], 571 | '--root': '/root', 572 | '--src': '/src', 573 | '--verbose': 0, 574 | '--version': False, 575 | '': 'buildstrap', 576 | '': ['requirements.txt'], 577 | 'debug': False, 578 | 'generate': False, 579 | 'run': False, 580 | 'show': True}, 581 | internal= OrderedDict([ 582 | ('buildout', 583 | OrderedDict([('newest', 'false'), 584 | ('parts', ListBuildout(['buildstrap'])), 585 | ('package', 'buildstrap'), 586 | ('extensions', 'gp.vcsdevelop'), 587 | ('directory', '/root'), 588 | ('develop', '/src'), 589 | ('eggs-directory', '/env/eggs'), 590 | ('develop-eggs-directory', '/env/develop-eggs'), 591 | ('parts-directory', '/env/parts'), 592 | ('develop-dir', '/env/develop'), 593 | ('bin-directory', '/bin'), 594 | ('requirements', '${buildout:develop}/requirements.txt'), 595 | ])), 596 | ('buildstrap', 597 | OrderedDict([('recipe', 'zc.recipe.egg'), 598 | ('eggs', 599 | ListBuildout(['${buildout:requirements-eggs}', 'buildstrap']))])), 600 | ]), 601 | output='\n'.join(['[buildout]', 602 | 'newest = false', 603 | 'parts = buildstrap', 604 | 'package = buildstrap', 605 | 'extensions = gp.vcsdevelop', 606 | 'directory = /root', 607 | 'develop = /src', 608 | 'eggs-directory = /env/eggs', 609 | 'develop-eggs-directory = /env/develop-eggs', 610 | 'parts-directory = /env/parts', 611 | 'develop-dir = /env/develop', 612 | 'bin-directory = /bin', 613 | 'requirements = ${buildout:develop}/requirements.txt', 614 | '', 615 | '[buildstrap]', 616 | 'recipe = zc.recipe.egg', 617 | 'eggs = ${buildout:requirements-eggs}', 618 | ' buildstrap', 619 | '', 620 | '']) 621 | ), 622 | 623 | 'config_debug_min': UnitConfig( 624 | args={'--bin': 'bin', 625 | '--config': '~/.config/buildstrap', 626 | '--env': 'var', 627 | '--force': True, 628 | '--help': False, 629 | '--interpreter': None, 630 | '--output': 'irrelevant', 631 | '--part': [], 632 | '--root': None, 633 | '--src': None, 634 | '--verbose': 0, 635 | '--version': False, 636 | '': 'buildstrap', 637 | '': ['requirements.txt'], 638 | 'debug': True, 639 | 'generate': False, 640 | 'run': False, 641 | 'show': False}, 642 | internal=OrderedDict([ 643 | ('buildout', 644 | OrderedDict([('newest', 'false'), 645 | ('parts', ListBuildout(['buildstrap'])), 646 | ('package', 'buildstrap'), 647 | ('extensions', 'gp.vcsdevelop'), 648 | ('develop', '.'), 649 | ('eggs-directory', '${buildout:directory}/var/eggs'), 650 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 651 | ('parts-directory', '${buildout:directory}/var/parts'), 652 | ('develop-dir', '${buildout:directory}/var/develop'), 653 | ('bin-directory', '${buildout:directory}/bin'), 654 | ('requirements', ListBuildout(['${buildout:develop}/requirements.txt']))])), 655 | ('buildstrap', 656 | OrderedDict([('recipe', 'zc.recipe.egg'), 657 | ('eggs', 658 | ListBuildout(['${buildout:requirements-eggs}', 'buildstrap']))])), 659 | ]), 660 | output='\n'.join(['[buildout]', 661 | 'newest = false', 662 | 'parts = buildstrap', 663 | 'package = buildstrap', 664 | 'extensions = gp.vcsdevelop', 665 | 'develop = .', 666 | 'eggs-directory = ${buildout:directory}/var/eggs', 667 | 'develop-eggs-directory = ${buildout:directory}/var/develop-eggs', 668 | 'parts-directory = ${buildout:directory}/var/parts', 669 | 'develop-dir = ${buildout:directory}/var/develop', 670 | 'bin-directory = ${buildout:directory}/bin', 671 | 'requirements = ${buildout:develop}/requirements.txt', 672 | '', 673 | '[buildstrap]', 674 | 'recipe = zc.recipe.egg', 675 | 'eggs = ${buildout:requirements-eggs}', 676 | ' buildstrap', 677 | '', 678 | '', 679 | ]) 680 | ), 681 | 682 | 'config_run_min': UnitConfig( 683 | args={'--bin': 'bin', 684 | '--config': '~/.config/buildstrap', 685 | '--env': 'var', 686 | '--force': True, 687 | '--help': False, 688 | '--interpreter': None, 689 | '--output': 'test_file', 690 | '--part': [], 691 | '--root': None, 692 | '--src': None, 693 | '--verbose': 0, 694 | '--version': False, 695 | '': 'buildstrap', 696 | '': ['requirements.txt'], 697 | 'debug': False, 698 | 'generate': False, 699 | 'run': True, 700 | 'show': False}, 701 | internal=OrderedDict([ 702 | ('buildout', 703 | OrderedDict([('newest', 'false'), 704 | ('parts', ListBuildout(['buildstrap'])), 705 | ('package', 'buildstrap'), 706 | ('extensions', 'gp.vcsdevelop'), 707 | ('develop', '.'), 708 | ('eggs-directory', '${buildout:directory}/var/eggs'), 709 | ('develop-eggs-directory', '${buildout:directory}/var/develop-eggs'), 710 | ('parts-directory', '${buildout:directory}/var/parts'), 711 | ('develop-dir', '${buildout:directory}/var/develop'), 712 | ('bin-directory', '${buildout:directory}/bin'), 713 | ('requirements', '${buildout:develop}/requirements.txt')])), 714 | ('buildstrap', 715 | OrderedDict([('recipe', 'zc.recipe.egg'), 716 | ('eggs', 717 | ListBuildout(['${buildout:requirements-eggs}', 'buildstrap']))])), 718 | ]), 719 | output='\n'.join(['[buildout]', 720 | 'newest = false', 721 | 'parts = buildstrap', 722 | 'package = buildstrap', 723 | 'extensions = gp.vcsdevelop', 724 | 'develop = .', 725 | 'eggs-directory = ${buildout:directory}/var/eggs', 726 | 'develop-eggs-directory = ${buildout:directory}/var/develop-eggs', 727 | 'parts-directory = ${buildout:directory}/var/parts', 728 | 'develop-dir = ${buildout:directory}/var/develop', 729 | 'bin-directory = ${buildout:directory}/bin', 730 | 'requirements = ${buildout:develop}/requirements.txt', 731 | '', 732 | '[buildstrap]', 733 | 'recipe = zc.recipe.egg', 734 | 'eggs = ${buildout:requirements-eggs}', 735 | ' buildstrap', 736 | '', 737 | '', 738 | ]) 739 | ) 740 | 741 | } 742 | 743 | import io 744 | class StringIO(io.StringIO): 745 | def __enter__(self, *args, **kwarg): 746 | self.seek(0) 747 | self.truncate(0) 748 | return super(StringIO, self).__enter__(*args, **kwarg) 749 | 750 | def __exit__(self, *args, **kwarg): 751 | pass 752 | 753 | class MockupsMixin: 754 | @contextmanager 755 | def mocked_open(self, target): 756 | import builtins 757 | import buildstrap.buildstrap 758 | 759 | buf = StringIO() 760 | orig_open = builtins.open 761 | 762 | def mock_open(fname, *args, **kwarg): 763 | if fname == target: 764 | # mimic open() by sending buffer as a generator 765 | return buf 766 | else: 767 | return orig_open(fname, *args, **kwarg) 768 | 769 | builtins.open = mock_open 770 | yield buf 771 | builtins.open = orig_open 772 | 773 | @contextmanager 774 | def mocked_os_path_exists(self, target, value): 775 | import os 776 | orig_exists = os.path.exists 777 | def mock_exists(f): 778 | if f == target: 779 | return value 780 | return orig_exists(f) 781 | os.path.exists = mock_exists 782 | yield 783 | os.path.exists = orig_exists 784 | 785 | @contextmanager 786 | def mocked_buildout(self): 787 | import buildstrap.buildstrap 788 | buildout_orig = buildstrap.buildstrap.buildout 789 | class MockBuildout: 790 | def __init__(self): 791 | self.ran = False 792 | b = MockBuildout() 793 | def mock_buildout(*args, **kwarg): 794 | b.ran = True 795 | return 796 | buildstrap.buildstrap.buildout = mock_buildout 797 | yield b 798 | buildstrap.buildstrap.buildout = buildout_orig 799 | 800 | 801 | class TestFun_generate_buildout_config(MockupsMixin): 802 | def test__config__empty__stdout(self, capsys): 803 | generate_buildout_config(parts={}, output='-') 804 | out, err = capsys.readouterr() 805 | assert out == '' 806 | 807 | def test__config__dummy__stdout(self, capsys): 808 | generate_buildout_config(parts={'foobar': {'foo': 'bar'}}, output='-') 809 | out, err = capsys.readouterr() 810 | assert out == '[foobar]\nfoo = bar\n\n' 811 | 812 | def test__config__empty__file(self, capsys): 813 | with self.mocked_open('test_file') as buf: 814 | generate_buildout_config(parts={}, output='test_file') 815 | assert buf.getvalue() == '' 816 | 817 | def test__config__dummy__fileexists(self): 818 | with self.mocked_os_path_exists('test_file', True): 819 | with self.mocked_open('test_file') as buf: 820 | with pytest.raises(FileExistsError): 821 | generate_buildout_config(parts={'foobar': {'foo': 'bar'}}, output='test_file') 822 | 823 | def test__config__dummy__fileexists_force(self): 824 | with self.mocked_os_path_exists('test_file', True): 825 | with self.mocked_open('test_file') as buf: 826 | generate_buildout_config(parts={'foobar': {'foo': 'bar'}}, output='test_file', force=True) 827 | assert buf.getvalue() == '[foobar]\nfoo = bar\n\n' 828 | 829 | def test__config__value__file(self, capsys): 830 | with self.mocked_buildout(): 831 | with self.mocked_open('test_file') as buf: 832 | for name, config in unit_config_list.items(): 833 | if name.startswith('config_debug_'): 834 | continue 835 | generate_buildout_config(parts=config.internal, output='test_file') 836 | assert buf.getvalue() == config.output 837 | 838 | def test__config__value__stdout(self): 839 | for name, config in unit_config_list.items(): 840 | with self.mocked_open('test_file') as buf: 841 | # skip tests for debug command (out of scope for the tested function) 842 | generate_buildout_config(parts=config.internal, output='test_file') 843 | assert buf.getvalue() == config.output 844 | 845 | class TestFun_test_buildstrap(MockupsMixin): 846 | def test_buildstrap(self, capsys): 847 | with self.mocked_buildout() as buildout_mock: 848 | for name, config in unit_config_list.items(): 849 | with self.mocked_os_path_exists(config.args['--output'], True): 850 | with self.mocked_open(config.args['--output']) as buf: 851 | buildstrap(config.args) 852 | out, err = capsys.readouterr() 853 | if config.args['show']: 854 | assert out == config.output 855 | elif config.args['debug']: 856 | import io 857 | fake_out = io.StringIO() 858 | pprint(config.internal, stream=fake_out) 859 | assert out == fake_out.getvalue() 860 | elif config.args['run']: 861 | assert buildout_mock.ran == True 862 | assert buf.getvalue() == config.output 863 | else: 864 | assert buf.getvalue() == config.output 865 | 866 | 867 | --------------------------------------------------------------------------------