metapensiero.tool.tinject


Namemetapensiero.tool.tinject JSON
Version 1.3 PyPI version JSON
download
home_page
SummaryAutomate creation of sources
upload_time2023-12-07 09:46:46
maintainer
docs_urlNone
author
requires_python>=3.9
licenseGPL-3.0-or-later
keywords yaml jinja2 scaffolding skeleton
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            .. -*- coding: utf-8 -*-
.. :Project:   metapensiero.tool.tinject -- Automate creation of sources
.. :Created:   Wed 13 Apr 2016 11:22:34 CEST
.. :Author:    Lele Gaifax <lele@metapensiero.it>
.. :License:   GNU General Public License version 3 or later
.. :Copyright: © 2016, 2017, 2018, 2022 Lele Gaifax
..

===========================
 metapensiero.tool.tinject
===========================

Automate creation of sources
============================

:author: Lele Gaifax
:contact: lele@metapensiero.it
:license: GNU General Public License version 3 or later

This is a simple tool that takes a YAML_ declarative configuration of `actions`, each
describing the set of `steps` needed to accomplish it, and realizes one or more actions
possibly interpolating a set of dynamic values inserted by the user.

I've written it with the goal of taking away, as much as possible, the tediousness of creating
new content in a PatchDB_ setup.

There is a myriad of similar tools around, but most of them are either targeted at
bootstrapping a hierarchy of files and directories (that is, a *one-shot* operation, for
example to create the *skeleton* of a new Python package, like cookiecutter_ to mention one) or
belong to a very specific domain (an example of this category may be ZopeSkel_).

To fulfill my goal I needed a tool able to perform an arbitrary list of operations, primarily:

* create a directory
* create a file with a particular content
* change the content of an existing file

It obviously needs to be *parametric* in almost every part: the pathnames and the actual
content of the files must be determined by a *template* interpolated with a set of *values*.

This is what Jinja2_ excels at.

Since the values are mostly *volatile* (that is, almost all of them change from one invocation
to the other), I first used the excellent `inquirer`_ library to prompt the user, but at some
point I was asked about compatibility with Windows and thus I switched to `whaaaaat`_, that
being based on `prompt_toolkit`_ was more promising on that. More recently, due to a long
inactivity on the port to version 2 of prompt_toolkit (see `whaaaaat's issue 23`_), I replaced
it with `questionary`_: thankfully they all expose a very similar interface, so that was very
easily achieved (hint: the latter two effectively inherit from the former).

.. contents::

.. _cookiecutter: https://pypi.python.org/pypi/cookiecutter
.. _inquirer: https://pypi.org/project/inquirer/
.. _jinja2: http://jinja.pocoo.org/
.. _patchdb: https://pypi.python.org/pypi/metapensiero.sphinx.patchdb
.. _prompt_toolkit: https://pypi.org/project/prompt_toolkit/
.. _questionary: https://pypi.org/project/questionary/
.. _whaaaaat's issue 23: https://github.com/finklabs/whaaaaat/issues/23
.. _whaaaaat: https://pypi.python.org/pypi/whaaaaat
.. _yaml: http://yaml.org/
.. _zopeskel: https://pypi.python.org/pypi/ZopeSkel


Configuration
-------------

The YAML configuration file may be composed by one or two *documents*: an optional *global
section* and a mandatory one containing the list of available `actions`, each of them being
basically a list of `steps`.


Global section
~~~~~~~~~~~~~~

The first *document* contains a set of optional global settings, in particular:

* the options to instantiate the Jinja2 *environment*
* a set of file *headers*, one for each kind of file
* a list of *steps* to be performed at every invocation


Jinja2 environment
++++++++++++++++++

To avoid clashes with the syntax used by Python's ``format()`` minilanguage, the `default
environment settings`__ are the following:

block_start_string
  The start of a block is ``<<``, instead of the default ``{%``

block_end_string
  The end of a block is ``>>``, instead of the default ``%}``

variable_start_string
  A variable is introduced by ``«``, instead of the classic ``{{``

variable_end_string
  A variable ends with ``»``, and not with the classic ``}}``

extensions
  The extension ``jinja2_time.TimeExtension`` is automatically selected

keep_trailing_newline
  By default the trailing newline is *not* discarded

Obviously any Jinja2 template defined by the configuration must adhere to these settings. If
you don't like them, or if you want define different ones, you can put an entry like the
following in the globals section of the configuration::

  jinja:
    block_start_string: "[["
    block_end_string: "]]"
    variable_start_string: "<<"
    variable_end_string: ">>"

__ http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment


File headers
++++++++++++

This is a map between a file *suffix* and a template that will be automatically injected in the
render's context as the variable ``header``. For example, should you need to generate a Pascal
unit, you could insert the following::

  headers:
    pas: |
      (*« "*" * 70 »
       * Project: «package_name»
       * Created: «timestamp»
       *« "*" * 70 »)


Initial steps
+++++++++++++

This is a list of steps to be executed unconditionally at startup. In particular it may be used
to gather some values from arbitrary places to initialize the *answers*, to prompt the user for
a common set of values and to define custom *steps*. As an example, you could say::

  steps:
    - python:
        script: |
          import os, pwd, sys

          class InitCustomAnswersWithoutPrompt(Step):
              def __init__(self, state, config):
                  super().__init__(state, config)
                  self.name = config['name']
                  self.value = config['value']

              def announce(self):
                  self.state.announce('*', "Inject %s=%s", self.name, self.value)

              def __call__(self, *args, **kwargs):
                  return {self.name: self.value, python_version: sys.version}

          register_step('initcustom', InitCustomAnswersWithoutPrompt)

          myself = pwd.getpwuid(os.getuid())
          state.answers['author_username'] = myself.pw_name
          state.answers['author_fullname'] = myself.pw_gecos.split(',')[0]

    ## Here you can execute the new kind of operation defined above

    - initcustom:
        name: "myextravar"
        value: "thevalue"


Actions
~~~~~~~

An *action* is identified by a unique name and carries an optional description, an optional set
of prompts specific to the action and a list of one or more steps.

The following is a complete example::

  create_top_level_setup_py:
    description: Create top level setup.py

    prompt:
      - package_name:
          message: The name of the Python package

    steps:
      - createdir:
          directory: src

      - createfile:
          directory: src
          filename: setup.py
          content: |
            # Hi, I'm the setup.py file for «package_name»


Steps
~~~~~

A *step* is some kind of *operation* that must be carried out. The name of the step identifies
the kind of operation, and its value is used to specify the needed parameters. So, in the
example above, we have two steps, ``createdir`` and ``createfile``, each requiring its specific
arguments map.

A step may be conditionally skipped specifying an expression in its ``when`` setting: if
present, the operation will be performed only when the expression evaluates to true.

This is the list of available operation kinds:

changefile
  Perform some quite simple changes to the content of an existing file.

  Required configuration:

    directory
      The directory containing the file to be changed

    filename
      The name of the existing file within the given directory

    changes
      A list of tweaks: there are currently just three types, one that *add* some content
      *before* a given *marker*, one to add the content *after* it and one that *insert* some
      content *between* a marker *and* another marker keeping the block sorted

  Example::

    - changefile:
        directory: src
        filename: listofitems.txt
        changes:
          - add: "«newitemname»\n"
            before: "\n;; items delimiter\n"
          - add: "«newitemname»\n"
            after: "\n;; reversed insertion order\n"

    - changefile:
        directory: src
        filename: __init__.py
        changes:
          - insert: "from .«table_name» import «table_name»\n"
            between: "\n## ⌄⌄⌄ tinject import marker ⌄⌄⌄, please don't remove!\n"
            and: "\n## ⌃⌃⌃ tinject import marker ⌃⌃⌃, please don't remove!\n"

createdir
  Create a directory and its parents.

  Required configuration:

    directory
      The directory to be created

  Example::

    - createdir:
        directory: src/my/new/package

createfile
  Create a new file with a particular content.

  Required configuration:

    directory
      The directory contained the file to be created

    filename
      The name of the new file

    content
      A Jinja2 template that will be rendered and written to the new file

  Example::

    - createfile:
        directory: "«docs_dir»/«schema_name»/tables"
        filename: "«table_name».sql"
        description: Structure of table «schema_name».«table_name»
        ## The template may be either inline or included from an external file
        content: !include 'table.sql'

prompt
  Ask the user for some information bits.

  Required configuration: a list of dictionaries, each representing a `questionary's
  question`__.

  Example::

    - prompt:
        - name_of_the_variable:
            message: Tell me the value
            default: "default value"

        - different_kind_of_input:
            message: Select the variant
            kind: list
            choices:
              - Big
              - Medium
              - Small

  __ https://github.com/tmbo/questionary#2-Dict-style-question-formulation

python
  Execute an arbitrary Python script.

  Required configuration:

    script
      The code of the script

  The script is executed with a context containing the class ``Step``, the function
  ``register_step`` and the global ``state`` of the program.

  See the `initial steps`_ above for an example.

repeat
  Repeat a list of substeps.

  Required configuration:

    steps
      The list of substeps to repeat

  Optional configuration:

    description
      A message string, emitted at the start, if given

    answers
      The name of variable holding a list of answers, when one substep is a ``prompt``

    count
      The number of iterations

    when
      A Jinja boolean expression: if given it's evaluated once before the loop begins, that
      gets executed only when it expression's value is true, otherwise no repetition happens at
      all; the expression may refer to previous answers, even those collected while looping
      (that is, the variable specified by the ``answers`` option)

    until
      A Jinja boolean expression: if given (and ``count`` is **not**), then the loop is
      terminated when the condition is false

    again_message
      When neither ``count`` nor ``until`` are specified, the step will explicitly ask
      confirmation about looping again, at the end of all substeps execution

  See ``examples/repeat.yml`` for an example.


Sample session
--------------

Create a new schema with a new table::

  $ tinject --verbose apply examples/patchdb.yml new_schema new_table

  * Execute Python script
  [?] Author fullname (author_fullname): Lele Gaifax
  [?] Author username (author_username): lele
  [?] Author email (author_email): «author_username»@example.com
  [?] Fully qualified package name (package_name): package.qualified.name
  [?] Timestamp (timestamp): << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>
  [?] Year (year): << now 'local', '%Y' >>
  [?] Distribution license (license): GNU General Public License version 3 or later
  [?] Copyright holder (copyright): © «year» «author_fullname»
  [?] Root directory of Sphinx documentation (docs_dir): docs/database
  [?] Root directory of SQLAlchemy model sources (model_dir): src/«package_name|replace(".","/")»

  =====================
   Create a new schema
  =====================
  [?] Name of the new schema (schema_name): public

  * Create directory docs/database/public

  * Create file docs/database/public/index.rst

  * Create directory docs/database/public/tables

  * Create file docs/database/public/tables/index.rst

  * Create directory src/package/qualified/name/entities/public

  * Create file src/package/qualified/name/entities/public/__init__.py

  * Create directory src/package/qualified/name/tables/public

  * Create file src/package/qualified/name/tables/public/__init__.py

  ====================
   Create a new table
  ====================
  [?] Schema name of the new table (schema_name): public
  [?] Name of the new table (table_name): things
  [?] Description of the new table (table_description): The table ``«schema_name».«table_name»`` contains...
  [?] Name of the corresponding entity (entity_name): Thing

  * Create file docs/database/public/tables/things.rst

  * Create file docs/database/public/tables/things.sql

  * Create file src/package/qualified/name/entities/public/thing.py

  * Change file src/package/qualified/name/entities/public/__init__.py

    - insert “from .thing import T…” between “## ⌄⌄⌄ tinject impo…” and “## ⌃⌃⌃ tinject impo…”

    - add “mapper(Thing, t.thi…” after “## ⌃⌃⌃ tinject impo…”

  * Create file src/package/qualified/name/tables/public/things.py

  * Change file src/package/qualified/name/tables/public/__init__.py

    - insert “from .things import …” between “## ⌄⌄⌄ tinject impo…” and “## ⌃⌃⌃ tinject impo…”

Verify::

  $ cat src/package/qualified/name/entities/public/__init__.py
  # -*- coding: utf-8 -*-
  # :Project:   package.qualified.name -- Entities in schema public
  # :Created:   mer 15 giu 2016 13:24:54 CEST
  # :Author:    Lele Gaifax <lele@example.com>
  # :License:   GNU General Public License version 3 or later
  # :Copyright: © 2016 Lele Gaifax
  #

  from sqlalchemy.orm import mapper

  from ...tables import public as t

  ## ⌄⌄⌄ tinject import marker ⌄⌄⌄, please don't remove!
  from .thing import Thing

  ## ⌃⌃⌃ tinject import marker ⌃⌃⌃, please don't remove!

  mapper(Thing, t.things, properties={
  })

Add another table::

  $ tinject --verbose apply examples/patchdb.yml new_table

  * Execute Python script
  [?] Author fullname (author_fullname): Lele Gaifax
  [?] Author username (author_username): lele
  [?] Author email (author_email): «author_username»@example.com
  [?] Fully qualified package name (package_name): package.qualified.name
  [?] Timestamp (timestamp): << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>
  [?] Year (year): << now 'local', '%Y' >>
  [?] Distribution license (license): GNU General Public License version 3 or later
  [?] Copyright holder (copyright): © «year» «author_fullname»
  [?] Root directory of Sphinx documentation (docs_dir): docs/database
  [?] Root directory of SQLAlchemy model sources (model_dir): src/«package_name|replace(".","/")»

  ====================
   Create a new table
  ====================
  [?] Schema name of the new table (schema_name): public
  [?] Name of the new table (table_name): thangs
  [?] Description of the new table (table_description): The table ``«schema_name».«table_name»`` contains...
  [?] Name of the corresponding entity (entity_name): Thang

  * Create file docs/database/public/tables/thangs.rst

  * Create file docs/database/public/tables/thangs.sql

  * Create file src/package/qualified/name/entities/public/thang.py

  * Change file src/package/qualified/name/entities/public/__init__.py

    - insert “from .thang import T…” between “## ⌄⌄⌄ tinject impo…” and “## ⌃⌃⌃ tinject impo…”

    - add “mapper(Thang, t.tha…” after “## ⌃⌃⌃ tinject impo…”

  * Create file src/package/qualified/name/tables/public/thangs.py

  * Change file src/package/qualified/name/tables/public/__init__.py

    - insert “from .thangs import …” between “## ⌄⌄⌄ tinject impo…” and “## ⌃⌃⌃ tinject impo…”

Verify::

  $ cat src/package/qualified/name/entities/public/__init__.py
  # -*- coding: utf-8 -*-
  # :Project:   package.qualified.name -- Entities in schema public
  # :Created:   mer 15 giu 2016 13:24:54 CEST
  # :Author:    Lele Gaifax <lele@example.com>
  # :License:   GNU General Public License version 3 or later
  # :Copyright: © 2016 Lele Gaifax
  #

  from sqlalchemy.orm import mapper

  from ...tables import public as t

  ## ⌄⌄⌄ tinject import marker ⌄⌄⌄, please don't remove!
  from .thang import Thang
  from .thing import Thing

  ## ⌃⌃⌃ tinject import marker ⌃⌃⌃, please don't remove!

  mapper(Thang, t.thangs, properties={
  })

  mapper(Thing, t.things, properties={
  })


Batch mode
----------

There are cases when all the *variables* are already known and thus there's no need to
interactively prompt the user to get the job done.

The ``apply`` action accepts the options ``--prompt-only`` and ``--answers-file`` to make that
possible.

The former can be used to collect needed information and print that back, or write them into a
YAML file with ``--output-answers``::

  $ tinject apply -p -o pre-answered.yml patchdb.yml new_schema
  ? Author fullname (author_fullname) Lele Gaifax
  ? Author username (author_username) lele
  ? Author email (author_email) «author_username»@example.com
  ? Fully qualified package name (package_name) package.qualified.name
  ? Timestamp (timestamp) << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>
  ? Year (year) << now 'local', '%Y' >>
  ? Distribution license (license) GNU General Public License version 3 or later
  ? Copyright holder (copyright) © «year» «author_fullname»
  ? Root directory of Sphinx documentation (docs_dir) docs/database
  ? Root directory of SQLAlchemy model sources (model_dir) src/«package_name|replace(".","/")»
  ? Name of the new schema (schema_name) public

If you put those settings into a YAML file, you can then execute the action in batch mode::

  $ tinject -v apply -a pre-answered.yml patchdb.yml new_schema

  * Execute Python script

  =====================
   Create a new schema
  =====================

  * Create directory docs/database/public

  * Create file docs/database/public/index.rst

  * Create directory docs/database/public/tables

  * Create file docs/database/public/tables/index.rst

  * Create directory src/package/qualified/name/entities/public

  * Create file src/package/qualified/name/entities/public/__init__.py

  * Create directory src/package/qualified/name/tables/public

  * Create file src/package/qualified/name/tables/public/__init__.py

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "metapensiero.tool.tinject",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "",
    "keywords": "YAML Jinja2 scaffolding skeleton",
    "author": "",
    "author_email": "Lele Gaifax <lele@metapensiero.it>",
    "download_url": "https://files.pythonhosted.org/packages/25/eb/0eb8fbd1a331f1b367bf6cf1b276f8b8c02b48342a8e65943c6d0ac30741/metapensiero_tool_tinject-1.3.tar.gz",
    "platform": null,
    "description": ".. -*- coding: utf-8 -*-\n.. :Project:   metapensiero.tool.tinject -- Automate creation of sources\n.. :Created:   Wed 13 Apr 2016 11:22:34 CEST\n.. :Author:    Lele Gaifax <lele@metapensiero.it>\n.. :License:   GNU General Public License version 3 or later\n.. :Copyright: \u00a9 2016, 2017, 2018, 2022 Lele Gaifax\n..\n\n===========================\n metapensiero.tool.tinject\n===========================\n\nAutomate creation of sources\n============================\n\n:author: Lele Gaifax\n:contact: lele@metapensiero.it\n:license: GNU General Public License version 3 or later\n\nThis is a simple tool that takes a YAML_ declarative configuration of `actions`, each\ndescribing the set of `steps` needed to accomplish it, and realizes one or more actions\npossibly interpolating a set of dynamic values inserted by the user.\n\nI've written it with the goal of taking away, as much as possible, the tediousness of creating\nnew content in a PatchDB_ setup.\n\nThere is a myriad of similar tools around, but most of them are either targeted at\nbootstrapping a hierarchy of files and directories (that is, a *one-shot* operation, for\nexample to create the *skeleton* of a new Python package, like cookiecutter_ to mention one) or\nbelong to a very specific domain (an example of this category may be ZopeSkel_).\n\nTo fulfill my goal I needed a tool able to perform an arbitrary list of operations, primarily:\n\n* create a directory\n* create a file with a particular content\n* change the content of an existing file\n\nIt obviously needs to be *parametric* in almost every part: the pathnames and the actual\ncontent of the files must be determined by a *template* interpolated with a set of *values*.\n\nThis is what Jinja2_ excels at.\n\nSince the values are mostly *volatile* (that is, almost all of them change from one invocation\nto the other), I first used the excellent `inquirer`_ library to prompt the user, but at some\npoint I was asked about compatibility with Windows and thus I switched to `whaaaaat`_, that\nbeing based on `prompt_toolkit`_ was more promising on that. More recently, due to a long\ninactivity on the port to version 2 of prompt_toolkit (see `whaaaaat's issue 23`_), I replaced\nit with `questionary`_: thankfully they all expose a very similar interface, so that was very\neasily achieved (hint: the latter two effectively inherit from the former).\n\n.. contents::\n\n.. _cookiecutter: https://pypi.python.org/pypi/cookiecutter\n.. _inquirer: https://pypi.org/project/inquirer/\n.. _jinja2: http://jinja.pocoo.org/\n.. _patchdb: https://pypi.python.org/pypi/metapensiero.sphinx.patchdb\n.. _prompt_toolkit: https://pypi.org/project/prompt_toolkit/\n.. _questionary: https://pypi.org/project/questionary/\n.. _whaaaaat's issue 23: https://github.com/finklabs/whaaaaat/issues/23\n.. _whaaaaat: https://pypi.python.org/pypi/whaaaaat\n.. _yaml: http://yaml.org/\n.. _zopeskel: https://pypi.python.org/pypi/ZopeSkel\n\n\nConfiguration\n-------------\n\nThe YAML configuration file may be composed by one or two *documents*: an optional *global\nsection* and a mandatory one containing the list of available `actions`, each of them being\nbasically a list of `steps`.\n\n\nGlobal section\n~~~~~~~~~~~~~~\n\nThe first *document* contains a set of optional global settings, in particular:\n\n* the options to instantiate the Jinja2 *environment*\n* a set of file *headers*, one for each kind of file\n* a list of *steps* to be performed at every invocation\n\n\nJinja2 environment\n++++++++++++++++++\n\nTo avoid clashes with the syntax used by Python's ``format()`` minilanguage, the `default\nenvironment settings`__ are the following:\n\nblock_start_string\n  The start of a block is ``<<``, instead of the default ``{%``\n\nblock_end_string\n  The end of a block is ``>>``, instead of the default ``%}``\n\nvariable_start_string\n  A variable is introduced by ``\u00ab``, instead of the classic ``{{``\n\nvariable_end_string\n  A variable ends with ``\u00bb``, and not with the classic ``}}``\n\nextensions\n  The extension ``jinja2_time.TimeExtension`` is automatically selected\n\nkeep_trailing_newline\n  By default the trailing newline is *not* discarded\n\nObviously any Jinja2 template defined by the configuration must adhere to these settings. If\nyou don't like them, or if you want define different ones, you can put an entry like the\nfollowing in the globals section of the configuration::\n\n  jinja:\n    block_start_string: \"[[\"\n    block_end_string: \"]]\"\n    variable_start_string: \"<<\"\n    variable_end_string: \">>\"\n\n__ http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment\n\n\nFile headers\n++++++++++++\n\nThis is a map between a file *suffix* and a template that will be automatically injected in the\nrender's context as the variable ``header``. For example, should you need to generate a Pascal\nunit, you could insert the following::\n\n  headers:\n    pas: |\n      (*\u00ab \"*\" * 70 \u00bb\n       * Project: \u00abpackage_name\u00bb\n       * Created: \u00abtimestamp\u00bb\n       *\u00ab \"*\" * 70 \u00bb)\n\n\nInitial steps\n+++++++++++++\n\nThis is a list of steps to be executed unconditionally at startup. In particular it may be used\nto gather some values from arbitrary places to initialize the *answers*, to prompt the user for\na common set of values and to define custom *steps*. As an example, you could say::\n\n  steps:\n    - python:\n        script: |\n          import os, pwd, sys\n\n          class InitCustomAnswersWithoutPrompt(Step):\n              def __init__(self, state, config):\n                  super().__init__(state, config)\n                  self.name = config['name']\n                  self.value = config['value']\n\n              def announce(self):\n                  self.state.announce('*', \"Inject %s=%s\", self.name, self.value)\n\n              def __call__(self, *args, **kwargs):\n                  return {self.name: self.value, python_version: sys.version}\n\n          register_step('initcustom', InitCustomAnswersWithoutPrompt)\n\n          myself = pwd.getpwuid(os.getuid())\n          state.answers['author_username'] = myself.pw_name\n          state.answers['author_fullname'] = myself.pw_gecos.split(',')[0]\n\n    ## Here you can execute the new kind of operation defined above\n\n    - initcustom:\n        name: \"myextravar\"\n        value: \"thevalue\"\n\n\nActions\n~~~~~~~\n\nAn *action* is identified by a unique name and carries an optional description, an optional set\nof prompts specific to the action and a list of one or more steps.\n\nThe following is a complete example::\n\n  create_top_level_setup_py:\n    description: Create top level setup.py\n\n    prompt:\n      - package_name:\n          message: The name of the Python package\n\n    steps:\n      - createdir:\n          directory: src\n\n      - createfile:\n          directory: src\n          filename: setup.py\n          content: |\n            # Hi, I'm the setup.py file for \u00abpackage_name\u00bb\n\n\nSteps\n~~~~~\n\nA *step* is some kind of *operation* that must be carried out. The name of the step identifies\nthe kind of operation, and its value is used to specify the needed parameters. So, in the\nexample above, we have two steps, ``createdir`` and ``createfile``, each requiring its specific\narguments map.\n\nA step may be conditionally skipped specifying an expression in its ``when`` setting: if\npresent, the operation will be performed only when the expression evaluates to true.\n\nThis is the list of available operation kinds:\n\nchangefile\n  Perform some quite simple changes to the content of an existing file.\n\n  Required configuration:\n\n    directory\n      The directory containing the file to be changed\n\n    filename\n      The name of the existing file within the given directory\n\n    changes\n      A list of tweaks: there are currently just three types, one that *add* some content\n      *before* a given *marker*, one to add the content *after* it and one that *insert* some\n      content *between* a marker *and* another marker keeping the block sorted\n\n  Example::\n\n    - changefile:\n        directory: src\n        filename: listofitems.txt\n        changes:\n          - add: \"\u00abnewitemname\u00bb\\n\"\n            before: \"\\n;; items delimiter\\n\"\n          - add: \"\u00abnewitemname\u00bb\\n\"\n            after: \"\\n;; reversed insertion order\\n\"\n\n    - changefile:\n        directory: src\n        filename: __init__.py\n        changes:\n          - insert: \"from .\u00abtable_name\u00bb import \u00abtable_name\u00bb\\n\"\n            between: \"\\n## \u2304\u2304\u2304 tinject import marker \u2304\u2304\u2304, please don't remove!\\n\"\n            and: \"\\n## \u2303\u2303\u2303 tinject import marker \u2303\u2303\u2303, please don't remove!\\n\"\n\ncreatedir\n  Create a directory and its parents.\n\n  Required configuration:\n\n    directory\n      The directory to be created\n\n  Example::\n\n    - createdir:\n        directory: src/my/new/package\n\ncreatefile\n  Create a new file with a particular content.\n\n  Required configuration:\n\n    directory\n      The directory contained the file to be created\n\n    filename\n      The name of the new file\n\n    content\n      A Jinja2 template that will be rendered and written to the new file\n\n  Example::\n\n    - createfile:\n        directory: \"\u00abdocs_dir\u00bb/\u00abschema_name\u00bb/tables\"\n        filename: \"\u00abtable_name\u00bb.sql\"\n        description: Structure of table \u00abschema_name\u00bb.\u00abtable_name\u00bb\n        ## The template may be either inline or included from an external file\n        content: !include 'table.sql'\n\nprompt\n  Ask the user for some information bits.\n\n  Required configuration: a list of dictionaries, each representing a `questionary's\n  question`__.\n\n  Example::\n\n    - prompt:\n        - name_of_the_variable:\n            message: Tell me the value\n            default: \"default value\"\n\n        - different_kind_of_input:\n            message: Select the variant\n            kind: list\n            choices:\n              - Big\n              - Medium\n              - Small\n\n  __ https://github.com/tmbo/questionary#2-Dict-style-question-formulation\n\npython\n  Execute an arbitrary Python script.\n\n  Required configuration:\n\n    script\n      The code of the script\n\n  The script is executed with a context containing the class ``Step``, the function\n  ``register_step`` and the global ``state`` of the program.\n\n  See the `initial steps`_ above for an example.\n\nrepeat\n  Repeat a list of substeps.\n\n  Required configuration:\n\n    steps\n      The list of substeps to repeat\n\n  Optional configuration:\n\n    description\n      A message string, emitted at the start, if given\n\n    answers\n      The name of variable holding a list of answers, when one substep is a ``prompt``\n\n    count\n      The number of iterations\n\n    when\n      A Jinja boolean expression: if given it's evaluated once before the loop begins, that\n      gets executed only when it expression's value is true, otherwise no repetition happens at\n      all; the expression may refer to previous answers, even those collected while looping\n      (that is, the variable specified by the ``answers`` option)\n\n    until\n      A Jinja boolean expression: if given (and ``count`` is **not**), then the loop is\n      terminated when the condition is false\n\n    again_message\n      When neither ``count`` nor ``until`` are specified, the step will explicitly ask\n      confirmation about looping again, at the end of all substeps execution\n\n  See ``examples/repeat.yml`` for an example.\n\n\nSample session\n--------------\n\nCreate a new schema with a new table::\n\n  $ tinject --verbose apply examples/patchdb.yml new_schema new_table\n\n  * Execute Python script\n  [?] Author fullname (author_fullname): Lele Gaifax\n  [?] Author username (author_username): lele\n  [?] Author email (author_email): \u00abauthor_username\u00bb@example.com\n  [?] Fully qualified package name (package_name): package.qualified.name\n  [?] Timestamp (timestamp): << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>\n  [?] Year (year): << now 'local', '%Y' >>\n  [?] Distribution license (license): GNU General Public License version 3 or later\n  [?] Copyright holder (copyright): \u00a9 \u00abyear\u00bb \u00abauthor_fullname\u00bb\n  [?] Root directory of Sphinx documentation (docs_dir): docs/database\n  [?] Root directory of SQLAlchemy model sources (model_dir): src/\u00abpackage_name|replace(\".\",\"/\")\u00bb\n\n  =====================\n   Create a new schema\n  =====================\n  [?] Name of the new schema (schema_name): public\n\n  * Create directory docs/database/public\n\n  * Create file docs/database/public/index.rst\n\n  * Create directory docs/database/public/tables\n\n  * Create file docs/database/public/tables/index.rst\n\n  * Create directory src/package/qualified/name/entities/public\n\n  * Create file src/package/qualified/name/entities/public/__init__.py\n\n  * Create directory src/package/qualified/name/tables/public\n\n  * Create file src/package/qualified/name/tables/public/__init__.py\n\n  ====================\n   Create a new table\n  ====================\n  [?] Schema name of the new table (schema_name): public\n  [?] Name of the new table (table_name): things\n  [?] Description of the new table (table_description): The table ``\u00abschema_name\u00bb.\u00abtable_name\u00bb`` contains...\n  [?] Name of the corresponding entity (entity_name): Thing\n\n  * Create file docs/database/public/tables/things.rst\n\n  * Create file docs/database/public/tables/things.sql\n\n  * Create file src/package/qualified/name/entities/public/thing.py\n\n  * Change file src/package/qualified/name/entities/public/__init__.py\n\n    - insert \u201cfrom .thing import T\u2026\u201d between \u201c## \u2304\u2304\u2304 tinject impo\u2026\u201d and \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\n    - add \u201cmapper(Thing, t.thi\u2026\u201d after \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\n  * Create file src/package/qualified/name/tables/public/things.py\n\n  * Change file src/package/qualified/name/tables/public/__init__.py\n\n    - insert \u201cfrom .things import \u2026\u201d between \u201c## \u2304\u2304\u2304 tinject impo\u2026\u201d and \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\nVerify::\n\n  $ cat src/package/qualified/name/entities/public/__init__.py\n  # -*- coding: utf-8 -*-\n  # :Project:   package.qualified.name -- Entities in schema public\n  # :Created:   mer 15 giu 2016 13:24:54 CEST\n  # :Author:    Lele Gaifax <lele@example.com>\n  # :License:   GNU General Public License version 3 or later\n  # :Copyright: \u00a9 2016 Lele Gaifax\n  #\n\n  from sqlalchemy.orm import mapper\n\n  from ...tables import public as t\n\n  ## \u2304\u2304\u2304 tinject import marker \u2304\u2304\u2304, please don't remove!\n  from .thing import Thing\n\n  ## \u2303\u2303\u2303 tinject import marker \u2303\u2303\u2303, please don't remove!\n\n  mapper(Thing, t.things, properties={\n  })\n\nAdd another table::\n\n  $ tinject --verbose apply examples/patchdb.yml new_table\n\n  * Execute Python script\n  [?] Author fullname (author_fullname): Lele Gaifax\n  [?] Author username (author_username): lele\n  [?] Author email (author_email): \u00abauthor_username\u00bb@example.com\n  [?] Fully qualified package name (package_name): package.qualified.name\n  [?] Timestamp (timestamp): << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>\n  [?] Year (year): << now 'local', '%Y' >>\n  [?] Distribution license (license): GNU General Public License version 3 or later\n  [?] Copyright holder (copyright): \u00a9 \u00abyear\u00bb \u00abauthor_fullname\u00bb\n  [?] Root directory of Sphinx documentation (docs_dir): docs/database\n  [?] Root directory of SQLAlchemy model sources (model_dir): src/\u00abpackage_name|replace(\".\",\"/\")\u00bb\n\n  ====================\n   Create a new table\n  ====================\n  [?] Schema name of the new table (schema_name): public\n  [?] Name of the new table (table_name): thangs\n  [?] Description of the new table (table_description): The table ``\u00abschema_name\u00bb.\u00abtable_name\u00bb`` contains...\n  [?] Name of the corresponding entity (entity_name): Thang\n\n  * Create file docs/database/public/tables/thangs.rst\n\n  * Create file docs/database/public/tables/thangs.sql\n\n  * Create file src/package/qualified/name/entities/public/thang.py\n\n  * Change file src/package/qualified/name/entities/public/__init__.py\n\n    - insert \u201cfrom .thang import T\u2026\u201d between \u201c## \u2304\u2304\u2304 tinject impo\u2026\u201d and \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\n    - add \u201cmapper(Thang, t.tha\u2026\u201d after \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\n  * Create file src/package/qualified/name/tables/public/thangs.py\n\n  * Change file src/package/qualified/name/tables/public/__init__.py\n\n    - insert \u201cfrom .thangs import \u2026\u201d between \u201c## \u2304\u2304\u2304 tinject impo\u2026\u201d and \u201c## \u2303\u2303\u2303 tinject impo\u2026\u201d\n\nVerify::\n\n  $ cat src/package/qualified/name/entities/public/__init__.py\n  # -*- coding: utf-8 -*-\n  # :Project:   package.qualified.name -- Entities in schema public\n  # :Created:   mer 15 giu 2016 13:24:54 CEST\n  # :Author:    Lele Gaifax <lele@example.com>\n  # :License:   GNU General Public License version 3 or later\n  # :Copyright: \u00a9 2016 Lele Gaifax\n  #\n\n  from sqlalchemy.orm import mapper\n\n  from ...tables import public as t\n\n  ## \u2304\u2304\u2304 tinject import marker \u2304\u2304\u2304, please don't remove!\n  from .thang import Thang\n  from .thing import Thing\n\n  ## \u2303\u2303\u2303 tinject import marker \u2303\u2303\u2303, please don't remove!\n\n  mapper(Thang, t.thangs, properties={\n  })\n\n  mapper(Thing, t.things, properties={\n  })\n\n\nBatch mode\n----------\n\nThere are cases when all the *variables* are already known and thus there's no need to\ninteractively prompt the user to get the job done.\n\nThe ``apply`` action accepts the options ``--prompt-only`` and ``--answers-file`` to make that\npossible.\n\nThe former can be used to collect needed information and print that back, or write them into a\nYAML file with ``--output-answers``::\n\n  $ tinject apply -p -o pre-answered.yml patchdb.yml new_schema\n  ? Author fullname (author_fullname) Lele Gaifax\n  ? Author username (author_username) lele\n  ? Author email (author_email) \u00abauthor_username\u00bb@example.com\n  ? Fully qualified package name (package_name) package.qualified.name\n  ? Timestamp (timestamp) << now 'local', '%a %d %b %Y %H:%M:%S %Z' >>\n  ? Year (year) << now 'local', '%Y' >>\n  ? Distribution license (license) GNU General Public License version 3 or later\n  ? Copyright holder (copyright) \u00a9 \u00abyear\u00bb \u00abauthor_fullname\u00bb\n  ? Root directory of Sphinx documentation (docs_dir) docs/database\n  ? Root directory of SQLAlchemy model sources (model_dir) src/\u00abpackage_name|replace(\".\",\"/\")\u00bb\n  ? Name of the new schema (schema_name) public\n\nIf you put those settings into a YAML file, you can then execute the action in batch mode::\n\n  $ tinject -v apply -a pre-answered.yml patchdb.yml new_schema\n\n  * Execute Python script\n\n  =====================\n   Create a new schema\n  =====================\n\n  * Create directory docs/database/public\n\n  * Create file docs/database/public/index.rst\n\n  * Create directory docs/database/public/tables\n\n  * Create file docs/database/public/tables/index.rst\n\n  * Create directory src/package/qualified/name/entities/public\n\n  * Create file src/package/qualified/name/entities/public/__init__.py\n\n  * Create directory src/package/qualified/name/tables/public\n\n  * Create file src/package/qualified/name/tables/public/__init__.py\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": "Automate creation of sources",
    "version": "1.3",
    "project_urls": {
        "Changelog": "https://gitlab.com/metapensiero/metapensiero.tool.tinject/-/blob/master/CHANGES.rst",
        "Source": "https://gitlab.com/metapensiero/metapensiero.tool.tinject"
    },
    "split_keywords": [
        "yaml",
        "jinja2",
        "scaffolding",
        "skeleton"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5df4df22f9f81bbab5ce537e3fe2bc8cb2c962adc9c2b6222962129b41fd3249",
                "md5": "129b4eacd4a3a85d75be2be698fda089",
                "sha256": "c75f105b81a8f9741393a991c7abf94f94f115758d910aa32e096ca429e07521"
            },
            "downloads": -1,
            "filename": "metapensiero_tool_tinject-1.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "129b4eacd4a3a85d75be2be698fda089",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 18673,
            "upload_time": "2023-12-07T09:46:43",
            "upload_time_iso_8601": "2023-12-07T09:46:43.925831Z",
            "url": "https://files.pythonhosted.org/packages/5d/f4/df22f9f81bbab5ce537e3fe2bc8cb2c962adc9c2b6222962129b41fd3249/metapensiero_tool_tinject-1.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "25eb0eb8fbd1a331f1b367bf6cf1b276f8b8c02b48342a8e65943c6d0ac30741",
                "md5": "c11135a77311cfb31c13db75ae1fb7d0",
                "sha256": "25d4cd28929a80842867f85c374112e601836e05dbdc4854399a0265d5b1a3b9"
            },
            "downloads": -1,
            "filename": "metapensiero_tool_tinject-1.3.tar.gz",
            "has_sig": false,
            "md5_digest": "c11135a77311cfb31c13db75ae1fb7d0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 23052,
            "upload_time": "2023-12-07T09:46:46",
            "upload_time_iso_8601": "2023-12-07T09:46:46.277658Z",
            "url": "https://files.pythonhosted.org/packages/25/eb/0eb8fbd1a331f1b367bf6cf1b276f8b8c02b48342a8e65943c6d0ac30741/metapensiero_tool_tinject-1.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-12-07 09:46:46",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "codeberg": false,
    "gitlab_user": "metapensiero",
    "gitlab_project": "metapensiero.tool.tinject",
    "lcname": "metapensiero.tool.tinject"
}
        
Elapsed time: 0.14898s