Extending Projy — Projy 0.1 documentation

### Navigation

- index
- previous |
- Projy 0.1 documentation »

# Extending Projy

Writing new templates and data collectors is easy. Let’s continue reviewing our example.

## Project templates

Here is the project template used to create a Python package:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    # -*- coding: utf-8 -*-
    """ Projy template for PythonPackage. """

    # system
    from datetime import date
    # parent class
    from projy.templates.ProjyTemplate import ProjyTemplate
    # collectors
    from projy.collectors.AuthorCollector import AuthorCollector
    from projy.collectors.AuthorMailCollector import AuthorMailCollector

    class PythonPackageTemplate(ProjyTemplate):
        """ Projy template class for PythonPackage. """

        def __init__(self):
            self.project_name = None

        def directories(self):
            """ Return the names of directories to be created. """
            directories_description = [
                self.project_name,
                self.project_name + '/' + self.project_name.lower(),
                self.project_name + '/docs',
            ]
            return directories_description

        def files(self):
            """ Return the names of files to be created. """
            files_description = [
                [ self.project_name,
                  'bootstrap',
                  'BootstrapScriptFileTemplate' ],
                [ self.project_name,
                  'CHANGES.txt',
                  'PythonPackageCHANGESFileTemplate' ],
                [ self.project_name,
                  'LICENSE.txt',
                  'GPL3FileTemplate' ],
                [ self.project_name,
                  'MANIFEST.in',
                  'PythonPackageMANIFESTFileTemplate' ],
                [ self.project_name,
                  'README.txt',
                  'READMEReSTFileTemplate' ],
                [ self.project_name,
                  'setup.py',
                  'PythonPackageSetupFileTemplate' ],
                [ self.project_name + '/' + self.project_name.lower(),
                  '__init__.py',
                  None ],
                [ self.project_name + '/docs',
                  'index.rst',
                  None ],
            ]
            return files_description

        def substitutes(self):
            """ Return the substitutions for the templating replacements. """
            author_collector = AuthorCollector()
            mail_collector = AuthorMailCollector()
            substitute_dict = dict(
                project = self.project_name,
                project_lower = self.project_name.lower(),
                date = date.today().isoformat(),
                author = author_collector.collect(),
                author_email = mail_collector.collect(),
            )
            return substitute_dict

To write a new template, you have to specify four parts:
  - the name of the template, which is the name of the class ;
  - the `directories`, `files` and `substitutes` functions.

When writing a new template, you can use the `self.project_name` variable which contains the name of the project as you typed it. In our example, it is `TowelStuff`.

### Name of the template

Here it is simply `PythonPackageTemplate`. This is the name you type in the command line plus `Template` at the end. The created template inherits from the father of all templates, the `ProjyTemplate` class.

### The directories function

`directories`()

  > Returns a tuple containing all the names of the directories to be created.

  Return type: | list of directory names

In our example, the created directories are `TowelStuff`, `TowelStuff/towelstuff` and `TowelStuff/docs`.

### The files function

`files`()

  > This function should return a tuple containing three informations for each file:
  >   - the directory the file is in. It is defined as in the directories function ;
  >   - the name of the file ;
  >   - the template of the file, which is not the same as the project template. See File templates.

  Return type: | list of file names

In our example, eight files are created:
  - `bootstrap` created from `BootstrapScriptFileTemplate` ;
  - `CHANGES.txt` created from `PythonPackageCHANGESFileTemplate` ;
  - `LICENSE.txt` created from `GPL3FileTemplate` ;
  - `MANIFEST.in` created from `PythonPackageMANIFESTFileTemplate` ;
  - `README.txt` created from `READMEReSTFileTemplate` ;
  - `setup.py` created from `PythonPackageSetupFileTemplate` ;
  - `__init__.py` into the `TowelStuff/towelstuff` directory, created from `PythonPackageSetupFileTemplate` ;
  - `index.rst` into the `TowelStuff/docs` directory, created empty.

Details on the content of each file is given on Usage.

### The substitutes function

`substitutes`()

  > This function should return a dictionary containing the string substitutions used in the template.

  Return type: | list of file names

In our example, the substitutions made in all the created files are:
  - `$project` is replaced by `TowelStuff` ;
  - `$project_lower` is replaced by `towelstuff` ;
  - `$date` is replaced by the current date, in the format 2012-11-23 ;
  - `$author` is replaced by what returns the `AuthorCollector` ;
  - `$author_email` is replaced by what returns the `AuthorMailCollector` ;

## File templates

From all the templated files we created, let’s see how the `PythonPackageSetupFileTemplate` is made. Here is its content:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # -*- coding: utf-8 -*-
    """ $project setup.py script """

    # system
    from distutils.core import setup
    from os.path import join, dirname

    setup(
        name='$project',
        version='0.1.0',
        author='$author',
        author_email='$author_email',
        packages=['$project_lower','$project_lower.test'],
        url='http://',
        license='LICENSE.txt',
        long_description=open(join(dirname(__file__), 'README.txt')).read(),
        install_requires=[''],
        test_suite='$project_lower.test',
    )

It is simply the file you want to create with the variables that will be substitute in the creation process. Each variable should begin by `$` as described in the Template mechanism. Nothing fancy on this side, as you can see.

## Data collectors

A data collector, as its name suggest, collects data. It is used by Projy to complete the File templates. Here is the data collector for the author data:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    # -*- coding: utf-8 -*-
    """ AuthorCollector class
        Tries to find the program user name, as accuratly as possible.

        Put the functions alphabetical order in the same order as their importance.
        For example here, author_from_git should be taken before author_from_system
        as it is probably better.
    """

    # system
    import getpass
    import os
    from subprocess import Popen, PIPE, CalledProcessError
    # parent class
    from projy.collectors.Collector import Collector

    class AuthorCollector(Collector):
        """ The AuthorCollector class. """

        def __init__(self):
            self.author = None

        def author_from_git(self):
            """ Get the author name from git information. """
            self.author = None
            try:
                # launch git command and get answer
                cmd = Popen(["git", "config", "--get", "user.name"], stdout=PIPE)
                stdoutdata = cmd.communicate()
                if (stdoutdata[0]):
                    self.author = stdoutdata[0].rstrip(os.linesep)
            except ImportError:
                pass
            except CalledProcessError:
                pass
            except OSError:
                pass

            return self.author

        def author_from_system(self):
            """ Get the author name from system information.
                This is just the user name, not the real name.
            """
            self.author = getpass.getuser()
            return self.author

A data collector defines as many functions as necessary. In the case of the author, two ways of finding it are written. The first uses git. As many users of Projy would probably use it, chances are that its configuration will reflect the author’s information. As a fallback in case git does not return the wanted data, the user name is taken as the system current user name. There are probably other methods to find it, so feel free to propose some more.

Functions are treated in the alphabetical order, which means that the most accurate functions should come before the least accurate ones. Of course, one may not always know what the most accurate way of finding a particular data is. Be smart then!

### Navigation

- index
- previous |
- Projy 0.1 documentation »