Welcome to mr.bob’s documentation!¶
Author: | Tom Lazar <tom@tomster.org>, Domen Kožar <domen@dev.si> |
---|---|
Source code: | github.com project |
Bug tracker: | github.com issues |
License: | BSD |
Generated: | January 27, 2015 |
Version: | 0.2.dev0 |
Introduction
mr.bob is a tool that takes a directory skeleton, copies over its directory structure to a target folder and can use the Jinja2 (or some other) templating engine to dynamically generate the files. Additionally, it can ask you questions needed to render the structure, or provide a config file to answer them.
mr.bob is meant to deprecate previous tools such as paster (PasteScript) and templer.
User guide¶
Installation¶
$ pip install mr.bob
Usage¶
Once you install mr.bob, the mrbob command is available:
$ mrbob --help
usage: mrbob [-h] [-O TARGET_DIRECTORY] [-v] [-c CONFIG] [-V] [-l] [-w] [-n]
[-q]
[template]
Filesystem template renderer
positional arguments:
template Template name to use for rendering. See http://mrbob.r
eadthedocs.org/en/latest/userguide.html#usage for a
guide to template syntax
optional arguments:
-h, --help show this help message and exit
-O TARGET_DIRECTORY, --target-directory TARGET_DIRECTORY
Where to output rendered structure. Defaults to
current directory
-v, --verbose Print more output for debugging
-c CONFIG, --config CONFIG
Configuration file to specify either [mr.bob] or
[variables] sections
-V, --version Display version number
-l, --list-questions List all questions needed for the template
-w, --remember-answers
Remember answers to .mrbob.ini file inside output
directory
-n, --non-interactive
Don't prompt for input. Fail if questions are required
but not answered
-q, --quiet Suppress all but necessary output
By default, the target directory is the current folder. The most basic use case is rendering a template from a relative folder:
$ mrbob ../template_folder/
Or from a package:
$ mrbob some.package:template_folder/
Or from a downloaded zip file:
$ mrbob https://example.com/templates/mytemplate.zip
Or from a relative path in a zip file:
$ mrbob https://github.com/iElectric/mr.bob/archive/master.zip#mr.bob-master/mrbob/template_sample
Sample template to try out¶
$ mrbob mrbob:template_sample/
Welcome to mr.bob interactive mode. Before we generate directory structure, some questions need to be answered.
Answer with a question mark to display help.
Value in square brackets at the end of the questions present default value if there is no answer.
--> How old are you? [24]:
--> What is your name?: Foobar
--> Enter password:
Generated file structure at /current/directory/
Listing all questions needed to have corresponding variable for a template¶
$ mrbob --list-questions mrbob:template_sample/
author.age.default = 24
author.age.help = We need your age information to render the template
author.age.question = How old are you?
author.name.question = What is your name?
author.name.required = True
author.password.command_prompt = getpass:getpass
author.password.question = Enter password
Remember answers to a config file¶
Running:
$ mrbob --remember-answers -O new_dir mrbob:template_sample/
...
When everything is done, all answers are stored in new_dir/.mrbob.ini so later you reuse them:
$ mrbob --config new_dir/.mrbob.ini -O new_dir another_template/
...
Using non-interactive mode¶
Sometimes you might want to automate a script and use mrbob. It is wise to tell mrbob to not prompt for any input. mrbob will use given answers and defaults if answers are missing. In case a question is required and doesn’t have a default, error will be thrown.
Configuration¶
Configuration is done with .ini style files. There are two sections for configuration: :term:mr.bob and :term:variables.
Example of global config file ~/.mrbob or command line parameter mrbob --config foo.ini.
[mr.bob]
verbose = True
[variables]
author.name = Domen Kožar
author.email = domen@dev.si
Specifying answers¶
To answer some questions from a config file instead of interactively. Given me.ini:
[variables]
author.name = Domen Kožar
author.email = domen@dev.si
author.age = 24
do:
$ mrbob --config me.ini mrbob:template_sample/
Specifying defaults¶
Sometimes you might want to override defaults for a template. Given me.ini:
[defaults]
author.name = Domen Kožar
author.email = domen@dev.si
author.age = 24
do:
$ mrbob --config me.ini mrbob:template_sample/
mrbob will as you questions but default values will be also taken from config file.
Remote configuration¶
Config file can also be loaded from a remote location:
$ mrbob --config https://raw.github.com/iElectric/mr.bob/master/mrbob/tests/example.ini mrbob:template_sample/
Configuration inheritance¶
Configuration can be specified in multiple ways. See flow of mr.bob on the documentation front page to know how options are preferred.
Nesting variables into namespaces called groups¶
All variables can be specified in namespaces, such as author.name. Currently namespaces don’t do anything special besides providing readability.
mr.bob section reference¶
Parameter | Default | Explanation |
---|---|---|
ignored_files | No patterns | Multiple Unix-style patterns to specify which files should be ignored: for instance, to ignore Vim swap files, specify *.swp |
ignored_directories | No patterns | Multiple Unix-style patterns to specify which directories should be ignored: for instance, to ignore a Git directory, specify .git |
non_interactive | False | Don’t prompt for input. Fail if questions are required but not answered |
quiet | False | Don’t output anything except necessary |
remember_answers | False | Write answers to .mrbob.ini file inside output directory |
verbose | False | Output more information, useful for debugging |
Collection of community managed templates¶
You are encouraged to use the bobtemplates.something Python egg namespace to write templates and contribute them to this list by making a pull request.
Writing your own template¶
Starting¶
Writing your own template is as easy as creating a .mrbob.ini that may contain questions. Everything else is extra. To start quickly, use the template starter that ships with mr.bob:
$ mrbob mrbob:template_starter/
Welcome to mr.bob interactive mode. Before we generate directory structure, some questions need to be answered.
Answer with a question mark to display help.
Value in square brackets at the end of the questions present default value if there is no answer.
--> How old are you? [24]:
--> What is your name?: Foobar
--> Enter password:
Generated file structure at /home/ielectric/code/mr.bob
See .mrbob.ini for sample questions and sample.txt.bob for sample rendering.
How it works¶
Files inside the structure can be just copied to destination, or they can be suffixed with .bob and the templating engine will be used to render them.
By default a slightly customized Jinja2 templating is used. The big differences are that variables are referenced with {{{ variable }}} instead of {{ variable }} and blocks are {{% if variable %}} instead of {% if variable %}. To read more about templating see Jinja2 documentation.
Variables can also be used on folder and file names. Surround variables with plus signs. For example foo/+author+/+age+.bob given variables author being Foo and age being 12, foo/Foo/12 will be rendered.
Templating engine can be changed by specifying renderer in mr.bob config section in dotted notation. It must be a callable that expects a text source as the first parameter and a dictionary of variables as the second.
When rendering the structure, permissions will be preserved for files.
Writing Questions¶
[question] section in .mrbob.ini specifies a schema for how [variables] are validated. Example speaks for itself:
[questions]
author.name.question = What is your name?
author.name.required = True
author.age.question = How old are you?
author.age.help = We need your age information to render the template
author.age.default = 24
author.password.question = Enter password
author.password.command_prompt = getpass:getpass
Questions will be asked in the order written in .mrbob.ini.
questions section reference¶
Parameter | Default | Explanation |
---|---|---|
name | Required. Unique identifier for the question | |
question | Required. Question given interactively to a user when generating structure | |
default | None | Default value when no answer is given. Can be a dotted notation |
required | False | Specify if question must be answered |
command_prompt | raw_input() | Function that accepts a question and asks user for the answer |
help | “” | Extra help returned when user inputs a question mark |
pre_ask_question | None | dotted notation function to run before asking the question |
post_ask_question | None | dotted notation function to run after asking the question (also does validation) |
Common needs for templating¶
Default value of the question is dynamic¶
Use something like:
[questions]
author.name.question = What's your name?
author.name.pre_ask_question = bobtemplates.mytemplate.hooks:pre_author
Where pre_author function will modify the question and provide new mrbob.configurator.Question.default.
Conditionally render a file¶
Use something like:
[template]
post_render = bobtemplates.mytemplate.hooks:delete_readme
And based on mrbob.Configurator.variables answers, delete a file or add one.
Based on the answer of the question do something¶
Use something like:
[questions]
author.name.question = What's your name?
author.name.post_ask_question = bobtemplates.mytemplate.hooks:post_author
Where post_author function will take mrbob.configurator.Configurator, question and it’s answer.
Ask a question based on answer of previous question¶
use post_ask_question and add another question (is that possible if we are looping through questions? -> While questions: questions.pop())
Hooks¶
A list of places where you can hook into the process flow and provide your custom code. All hooks can have multiple entries limited by whitespace.
Post render hook¶
If you would like to execute a custom Python script after rendering is complete, you can use post_render hook in your .mrbob.ini.
[template]
post_render = bobtemplates.mytemplate.hooks:my_post_render_function
This assumes you have a bobtemplate.mytemplate egg with a hooks.py module. This module contains a my_post_render_hook function, which gets called after mr.bob has finished rendering your template.
The function expects one argument (mrbob.configurator.Configurator) and looks something like this:
def my_post_render_function(configurator):
if configurator.variables['author.email']:
# do something here
Pre render hook¶
Much like the Post render hook example above, you can use pre_render variable in your .mrbob.ini to specify a function to call before rendering starts.
[template]
pre_render = bobtemplates.mytemplate.hooks:my_pre_render_function
Pre question hook¶
For maximum flexibility, mr.bob allows you to set hooks to questions. Using pre_ask_question in your .mrbob.ini allows you to run custom code before a certain question.
- The function expects two arguments:
[questions]
author.name.question = What's your name?
author.name.pre_ask_question = bobtemplates.mytemplate.hooks:pre_author
def set_fullname(configurator, question):
question.default = 'foobar'
If you want question to be skipped, simply raise mrbob.bobexceptions.SkipQuestion inside your hook.
Post question hook¶
Similar to Pre question hook example above, you can use post_ask_question variable in your .mrbob.ini to specify a function to call after a question has been asked. Post question hook must return the answer of the question.
- The function expects three arguments:
- mrbob.configurator.Question
- mrbob.configurator.Configurator
- answer from the question
[questions]
author.firstname.question = What's your name?
author.lastname.question = What's your surname?
author.lastname.post_ask_question = bobtemplates.mytemplate.hooks:set_fullname
def set_fullname(configurator, question, answer):
configurator.variables['author.fullname'] =
configurator.variables['author.firstname'] + ' ' +
answer
return answer
Raise mrbob.bobexceptions.ValidationError to re-ask the question.
Hooks shipped with mr.bob¶
See mrbob.hooks.
template section reference¶
Parameter | Default | Explanation |
---|---|---|
renderer | mrbob.rendering:jinja2_renderer | Function for rendering templates in dotted notation |
pre_render | None | dotted notation function to run before rendering the templates |
post_render | None | dotted notation function to run after rendering the templates |
Design goals¶
- Cover 80% of use cases, don’t become too complex
- Ability to use templates not only from eggs, but also folders and similar
- Python 3 support
- Jinja2 renderer by default, but replaceable
- Ability to render multiple templates to the same target directory
Why another tool¶
- PasteScript is a big package with lots of legacy code and noone seems to care about maintaining it (and porting it to python3)
- a tool should do one thing and that thing good, which is where PasteScript fails
- PasteScript works only with Python eggs, mr.bob can also render templates from folder and zip files
- PasteScript uses Cheetah which doesn’t work on PyPy and has C extensions that need to be compiled
- PasteScript in unmaintainable, with really dodgy code
- PasteScript doesn’t preserve permissions when copying/rendering files
- mr.bob is just 200 lines of code with some extra features in mind that PasteScript cannot provide, such as a Python API for use by higher level libraries
Developer guide¶
Setup developer environment¶
$ git clone https://github.com/iElectric/mr.bob.git
$ cd mrbob
$ virtualenv .
$ source bin/activate
$ python setup.py develop
$ easy_install mr.bob[test,development]
$ mrbob --help
Source documentation¶
Warning
Python API is far from being “frozen”, use it with zero backwards-compatibility in mind. You are welcome to report suggestions to bug tracker.
mrbob – Main package¶
mrbob.configurator – Machinery to figure out configuration¶
- class mrbob.configurator.Configurator(template, target_directory, bobconfig=None, variables=None, defaults=None)[source]¶
Bases: object
Controller that figures out settings, asks questions and renders the directory structure.
Parameters: - template – Template name
- target_directory – Filesystem path to a output directory
- bobconfig – Configuration for mr.bob behaviour
- variables – Given variables to questions
- defaults – Overriden defaults of the questions
Additional to above settings, Configurator exposes following attributes:
- template_dir is root directory of the template
- is_tempdir if template directory is temporary (when using zipfile)
- templateconfig dictionary parsed from template section
- questions ordered list of `Question instances to be asked
- bobconfig dictionary parsed from mrbob section of the config
- render()[source]¶
Render file structure given instance configuration. Basically calls mrbob.rendering.render_structure().
- class mrbob.configurator.Question(name, question, default=None, required=False, command_prompt=<built-in function raw_input>, pre_ask_question='', post_ask_question='', help='', **extra)[source]¶
Bases: object
Question configuration. Parameters are used to configure questioning and possible validation of the answer.
Parameters: - name – Unique, namespaced name of the question
- question – Question to be asked
- default – Default value of the question
- required (bool) – Is question required?
- command_prompt – Function to executed to ask the question given question text
- help – Optional help message
- pre_ask_question – Space limited functions in dotted notation to ask before the question is asked
- post_ask_question – Space limited functions in dotted notation to ask aster the question is asked
- **extra –
Any extra parameters stored for possible extending of Question functionality
Any of above parameters can be accessed as an attribute of Question instance.
- ask(configurator)[source]¶
Eventually, ask the question.
Parameters: configurator – mrbob.configurator.Configurator instance
mrbob.bobexceptions – Exceptions¶
mr.bob exceptions module.
- exception mrbob.bobexceptions.ConfigurationError[source]¶
Bases: mrbob.bobexceptions.MrBobError
Raised during configuration phase
- exception mrbob.bobexceptions.SkipQuestion[source]¶
Bases: mrbob.bobexceptions.MrBobError
Raised during pre_ask_question if we should skip it
- exception mrbob.bobexceptions.TemplateConfigurationError[source]¶
Bases: mrbob.bobexceptions.ConfigurationError
Raised reading template configuration
- exception mrbob.bobexceptions.ValidationError[source]¶
Bases: mrbob.bobexceptions.MrBobError
Raised during question validation
mrbob.parsing – Parsing .ini files¶
mrbob.rendering – Everything related to rendering templates and directory structure¶
- mrbob.rendering.render_structure(fs_source_root, fs_target_root, variables, verbose, renderer, ignored_files, ignored_directories)[source]¶
Recursively copies the given filesystem path fs_source_root_ to a target directory `fs_target_root.
Any files ending in .bob are rendered as templates using the given renderer using the variables dictionary, thereby losing the .bob suffix.
strings wrapped in + signs in file- or directory names will be replaced with values from the variables, i.e. a file named +name+.py.bob given a dictionary {‘name’: ‘bar’} would be rendered as bar.py.
mrbob.hooks – Included hooks¶
Use any of hooks below or write your own. You are welcome to contribute them!
- mrbob.hooks.set_current_datetime(configurator, question)[source]¶
If you want to set the default answer of a question to the current date and time, use this function as Pre question hook:
[questions] project.question = What year was the project started? project.pre_ask_question = mrbob.hooks:set_current_datetime project.datetime_format = %%Y
The datetime_format property should be of the standard Python strftime format. It defaults to YYYY-MM-DD if not specified.
Please note that you’ll have to escape the % character (by using %%) due to the fact it’s a special character in INI files.
See the following URL for more information: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
- mrbob.hooks.show_message(configurator)[source]¶
If you want to display a message to the user when rendering is complete, you can use this function as Post render hook:
[template] post_render = mrbob.hooks:show_message message = Well done, %(author.name)s, your code is ready!
As shown above, you can use standard Python formatting in post_render_msg.
- mrbob.hooks.to_boolean(configurator, question, answer)[source]¶
If you want to convert an answer to Python boolean, you can use this function as Post question hook:
[questions] idiot.question = Are you young? idiot.post_ask_question = mrbob.hooks:to_boolean
Following variables can be converted to a boolean: y, n, yes, no, true, false, 1, 0
- mrbob.hooks.to_decimal(configurator, question, answer)[source]¶
If you want to convert an answer to Python float, you can use this function as Post question hook:
[questions] daemon.question = What interval should the daemon poll for data? daemon.post_ask_question = mrbob.hooks:to_decimal
- mrbob.hooks.to_integer(configurator, question, answer)[source]¶
If you want to convert an answer to Python integer, you can use this function as Post question hook:
[questions] owner.question = What's your age? owner.post_ask_question = mrbob.hooks:to_integer
- mrbob.hooks.validate_choices(configurator, question, answer)[source]¶
If you want to validate an answer using a limited set of choices, you can use this function as Post question hook:
[questions] license.question = What license would you like to use? license.post_ask_question = mrbob.hooks:validate_choices license.choices = MIT|BSD|Apache 2.0|Other license.choices_case_sensitive = yes license.choices_delimiter = |
Currently choices are split using whitespace by default. If you wish to have whitespace within each choice, you may specify a custom delimiter which will be used to split the choices.
This hook may be set to validate the choices in a case sensitive manner. However, this behaviour is disabled by default.
- mrbob.hooks.validate_datetime(configurator, question, answer)[source]¶
If you want to validate a date using a chosen date format, you can use this function as Post question hook:
[questions] project.question = What year was the project started? project.post_ask_question = mrbob.hooks:validate_datetime project.datetime_format = %%Y
The datetime_format property should be of the standard Python strftime format. It defaults to YYYY-MM-DD if not specified.
Please note that you’ll have to escape the % character (by using %%) due to the fact it’s a special character in INI files.
See the following URL for more information: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
- mrbob.hooks.validate_regex(configurator, question, answer)[source]¶
If you want to validate an answer using a regular expression, you can use this function as Post question hook:
[questions] project.question = Please specify a name (only lowercase characters)? project.post_ask_question = mrbob.hooks:validate_regex project.regex = ^[a-z]+$
Changelog¶
0.2 (unreleased)¶
- Nothing changed yet.
0.1.1 (2014-03-03)¶
- Claim Python 3.4 support [Domen Kožar]
- Fix encoding errors on Python 3 during installation [Domen Kožar]
- Use Jinja2 < 2.7 only for python 3.2 [Domen Kožar]
0.1 (2014-03-02)¶
- move exceptions to bobexceptions [Jean-Philippe Camguilhem]
- Use jinja2 < 2.7, since 2.7+ doesn’t support Python 3.2 [Domen Kožar]
- Filename variable name substitution regex failed to compile on windows [Domen Kožar]
- Do not copy .DS_Store. [Godefroid Chapelle]
- Configure patterns of files to ignore through ignored_files option of mr.bob section. [Godefroid Chapelle]
- Configure patterns of directories to ignore through ignored_directories option of mr.bob section. [Fotis Gimian]
- Added new post-question hook mrbob.hooks.to_integer [Fotis Gimian]
- Added new post-question hook mrbob.hooks.to_decimal [Fotis Gimian]
- Added new post-question hook mrbob.hooks.validate_choices [Fotis Gimian]
- Added new post-question hook mrbob.hooks.validate_regex [Fotis Gimian]
- Added new pre-question hook mrbob.hooks.set_current_datetime [Fotis Gimian]
- Added new post-question hook mrbob.hooks.validate_datetime [Fotis Gimian]
0.1a9 (2013-04-26)¶
- Regex to detect variable names when rendering file names was broken when directory path contains pluses. [Godefroid Chapelle]
0.1a8 (2013-03-11)¶
- Depend on six>=1.2.0 [Domen Kožar]
- Moved all exceptions to mrbob.exceptions module [Domen Kožar]
- Fix loading of zip files [Domen Kožar]
- #28: Remote loading of config files [Nejc Zupan]
- #30: Keep newlines of rendered template [Domen Kožar]
0.1a7 (2013-01-23)¶
- Don’t depend on argparse in python 2.7 and higher, since it’s already in stdlib [Domen Kožar]
- #22: Prevent users from specifying target directory inside template dir [Domen Kožar]
0.1a6 (2013-01-02)¶
- Use StrictUndefined with jinja2 renderer so that any missing key is reported as an error [Domen Kožar]
- If a key in a namespace was missing while rendering, no error was raised [Domen Kožar]
- Added hook mrbob.hooks.show_message [Domen Kožar]
- mrbob.validators.boolean renamed to mrbob.hooks.to_boolean [Domen Kožar]
- Renamed validators.py to hooks.py [Domen Kožar]
- Removed validators and action settings from [questions] as it is superseded by hooks [Domen Kožar]
- Added pre_ask_question and post_ask_question to [questions] section [Domen Kožar]
- Added pre_render, post_render and post_render_msg options [Domen Kožar]
- Added [defaults] section that will override template defaults. The only difference to [variables] is that variables provide default answers [Domen Kožar]
- Moved renderer parameter to [template] section [Domen Kožar]
- Added [template] section that is parsed only from .mrbob.ini inside a template directory. [Domen Kožar]
- Correctly evaluate boolean of quiet and verbose settings [Domen Kožar]
- Added non_interactive setting that will not prompt for any input and fail if any of required questions are not answered [Domen Kožar]
- Added remember_answers setting that will create .mrbob.ini file inside rendered directory with all the answers written to [variables] section [Domen Kožar]
- Include changelog in documentation [Domen Kožar]
- Question does no longer raise error if unknown parameter is passed from a config file. Instead those parameters are saved to question.extra that can be later inspected and validated. This is first step to have advanced question types such as question with a set of predefined answers [Domen Kožar]
- Rewrite all py.test stuff to nosetests, so we have unified testing now. This also fixes flake8 segfaults on pypy [Domen Kožar]
0.1a5 (2012-12-12)¶
- #26: Variables were not correctly parsed from config files [Domen Kožar]
0.1a4 (2012-12-11)¶
- Fix MANIFEST.in so that template examples are also included with distribution [Domen Kožar]
- Add -q/–quiet option to suppress output which isn’t strictly necessary [Sasha Hart]
- Suppress the interactive-mode welcome banner if there are no questions to ask [Sasha Hart]
- Don’t raise KeyError: ‘questions_order’ if [questions] is missing in an ini [Sasha Hart]
0.1a3 (2012-11-30)¶
- #13: Read user config from ~/.mrbob (as stated in docs and inline comments). [Andreas Kaiser]
0.1a2 (2012-11-29)¶
- #12: Fix unicode errors when using non-ASCII in questions or defaults [Domen Kožar]
- Ask questions in same order they were defined in template configuration file [Domen Kožar]
0.1a1 (2012-10-19)¶
- Initial release. [Domen Kožar, Tom Lazar]
Glossary¶
- dotted notation
- Importable Python function specified with dots as importing a module separated with a column to denote a function. For example mrbob.rendering:render_structure
- mr.bob
- This section configures how mrbob behaves
- variables
- This section answers to the questions that will be passed to templates for rendering