Skip to content

Commit

Permalink
Merge pull request #145 from stephenfin/add-subcommands
Browse files Browse the repository at this point in the history
Add 'towncrier create', 'towncrier check' commands
  • Loading branch information
hawkowl authored Aug 11, 2019
2 parents 2cf6dd6 + cad5d5f commit 83c33da
Show file tree
Hide file tree
Showing 15 changed files with 473 additions and 195 deletions.
32 changes: 25 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Hear ye, hear ye, says the ``towncrier``
``towncrier`` is a utility to produce useful, summarised news files for your project.
Rather than reading the Git history as some newer tools to produce it, or having one single file which developers all write to, ``towncrier`` reads "news fragments" which contain information `useful to end users`.


Philosophy
----------

Expand All @@ -34,34 +35,51 @@ Install from PyPI::
It is usable by projects written in other languages, provided you give it the version of the project when invoking it.
For Python 2/3 compatible projects, the version can be discovered automatically.

In your project root, add a ``pyproject.toml`` file, with the contents::
In your project root, add a ``pyproject.toml`` file.
You can configure your project in two ways.
To configure it via an explicit directory, add::


[tool.towncrier]
directory = "changes"

Alternatively, to configure it relative to a (Python) package directory, add::

[tool.towncrier]
package = "mypackage"
package_dir = "src"
filename = "NEWS.rst"

Then put news fragments (see "News Fragments" below) into a "newsfragments" directory under your package (so, if your project is named "myproject", and it's kept under ``src``, your newsfragments dir would be ``src/myproject/newsfragments/``).
For the latter, news fragments (see "News Fragments" below) should be in a ``newsfragments`` directory under your package.
Using the above example, your news fragments would be ``src/myproject/newsfragments/``).

To prevent git from removing the newsfragments directory, make a ``.gitignore`` file in it with::
.. tip::

!.gitignore
To prevent git from removing the ``newsfragments`` directory, make a ``.gitignore`` file in it with::

This will keep the folder around, but otherwise "empty".
!.gitignore

This will keep the folder around, but otherwise "empty".

``towncrier`` needs to know what version your project is, and there are two ways you can give it:

- For Python 2/3 compatible projects, a ``__version__`` in the top level package.
This can be either a string literal, a tuple, or an `Incremental <https://github.com/hawkowl/incremental>`_ version.

- Manually passing ``--version=<myversionhere>`` when interacting with ``towncrier``.

To create a new news fragment, use the ``towncrier create`` command.
For example::

towncrier create 123.feature

To produce a draft of the news file, run::

towncrier --draft
towncrier build --draft

To produce the news file for real, run::

towncrier
towncrier build

This command will remove the news files (with ``git rm``) and append the built news to the filename specified in ``towncrier.ini``, and then stage the news file changes (with ``git add``).
It leaves committing the changes up to the user.
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
maintainer='Amber Brown',
maintainer_email='hawkowl@twistedmatrix.com',
url="https://github.com/hawkowl/towncrier",
classifiers = [
classifiers=[
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 2",
Expand All @@ -21,7 +21,8 @@
use_incremental=True,
setup_requires=['incremental'],
install_requires=[
'Click',
'click',
'click-default-group',
'incremental',
'jinja2',
'toml',
Expand All @@ -35,7 +36,7 @@
long_description=open('README.rst').read(),
entry_points={
'console_scripts': [
'towncrier = towncrier:_main',
'towncrier = towncrier._shell:cli',
],
}
)
154 changes: 0 additions & 154 deletions src/towncrier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,160 +5,6 @@
towncrier, a builder for your news files.
"""

from __future__ import absolute_import, division

import os
import click
import pkg_resources

from datetime import date

from ._settings import load_config_from_file
from ._builder import find_fragments, split_fragments, render_fragments
from ._project import get_version, get_project_name
from ._writer import append_to_newsfile
from ._git import remove_files, stage_newsfile
from ._version import __version__


def _get_date():
return date.today().isoformat()


@click.command()
@click.option(
"--draft",
"draft",
default=False,
flag_value=True,
help=("Render the news fragments, don't write to files, " "don't check versions."),
)
@click.option(
"--config",
"config_file",
default='pyproject.toml',
help='Configuration file name.')
@click.option("--dir", "directory", default=".")
@click.option("--name", "project_name", default=None)
@click.option(
"--version",
"project_version",
default=None,
help="Render the news fragments using given version.",
)
@click.option("--date", "project_date", default=None)
@click.option(
"--yes",
"answer_yes",
default=False,
flag_value=True,
help="Do not ask for confirmation to remove news fragments.",
)
def _main(draft, directory, config_file, project_name, project_version, project_date, answer_yes):
return __main(
draft, directory, config_file, project_name, project_version, project_date, answer_yes
)


def __main(draft, directory, config_file, project_name, project_version, project_date, answer_yes):
"""
The main entry point.
"""
directory = os.path.abspath(directory)
config = load_config_from_file(os.path.join(directory, config_file))
to_err = draft

click.echo("Loading template...", err=to_err)
if config.get("template") is None:
template = pkg_resources.resource_string(
__name__, "templates/template.rst"
).decode("utf8")
else:
with open(config["template"], "rb") as tmpl:
template = tmpl.read().decode("utf8")

click.echo("Finding news fragments...", err=to_err)

definitions = config["types"]

if config.get("directory"):
base_directory = os.path.abspath(config["directory"])
fragment_directory = None
else:
base_directory = os.path.abspath(
os.path.join(directory, config["package_dir"], config["package"])
)
fragment_directory = "newsfragments"

fragments, fragment_filenames = find_fragments(
base_directory, config["sections"], fragment_directory, definitions
)

click.echo("Rendering news fragments...", err=to_err)
fragments = split_fragments(fragments, definitions)

if project_version is None:
project_version = get_version(
os.path.join(directory, config["package_dir"]), config["package"]
).strip()

if project_name is None:
package = config.get("package")
if package:
project_name = get_project_name(
os.path.abspath(os.path.join(directory, config["package_dir"])), package
)
else:
# Can't determine a project_name, but maybe it is not needed.
project_name = ""

if project_date is None:
project_date = _get_date().strip()

if config["title_format"]:
top_line = config["title_format"].format(
name=project_name, version=project_version, project_date=project_date
)
top_line += u"\n" + (config["underlines"][0] * len(top_line)) + u"\n"
else:
top_line = ""

rendered = render_fragments(
# The 0th underline is used for the top line
template,
config["issue_format"],
fragments,
definitions,
config["underlines"][1:],
config["wrap"],
{"name": project_name, "version": project_version, "date": project_date},
top_underline=config["underlines"][0],
)

if draft:
click.echo(
"Draft only -- nothing has been written.\n"
"What is seen below is what would be written.\n",
err=to_err,
)
if top_line:
click.echo("\n%s\n%s" % (top_line, rendered))
else:
click.echo(rendered)
else:
click.echo("Writing to newsfile...", err=to_err)
start_line = config["start_line"]
append_to_newsfile(
directory, config["filename"], start_line, top_line, rendered
)

click.echo("Staging newsfile...", err=to_err)
stage_newsfile(directory, config["filename"])

click.echo("Removing news fragments...", err=to_err)
remove_files(fragment_filenames, answer_yes)

click.echo("Done!", err=to_err)


__all__ = ["__version__"]
5 changes: 3 additions & 2 deletions src/towncrier/_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ def find_fragments(base_directory, sections, fragment_directory, definitions):

for basename in files:

ticket, category, counter = parse_newfragment_basename(basename,
definitions)
ticket, category, counter = parse_newfragment_basename(
basename, definitions
)
if category is None or category not in definitions:
continue

Expand Down
2 changes: 1 addition & 1 deletion src/towncrier/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def load_config_from_file(from_file):


def parse_toml(config):
if 'tool' not in config:
if "tool" not in config:
raise ValueError("No [tool.towncrier] section.")

config = config["tool"]["towncrier"]
Expand Down
23 changes: 23 additions & 0 deletions src/towncrier/_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) Stephen Finucane, 2019
# See LICENSE for details.

"""
towncrier, a builder for your news files.
"""

import click
from click_default_group import DefaultGroup

from .build import _main as _build_cmd
from .check import _main as _check_cmd
from .create import _main as _create_cmd


@click.group(cls=DefaultGroup, default="build", default_if_no_args=True)
def cli():
pass


cli.add_command(_build_cmd)
cli.add_command(_check_cmd)
cli.add_command(_create_cmd)
2 changes: 1 addition & 1 deletion src/towncrier/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@

from incremental import Version

__version__ = Version('towncrier', 19, 2, 0)
__version__ = Version("towncrier", 19, 2, 0)
__all__ = ["__version__"]
Loading

0 comments on commit 83c33da

Please sign in to comment.