scriv 1.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
scriv/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ Scriv changelog management tool.
3
+ """
4
+
5
+ __version__ = "1.6.0"
scriv/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Enable 'python -m scriv'."""
2
+
3
+ from .cli import cli
4
+
5
+ cli(prog_name="scriv")
scriv/changelog.py ADDED
@@ -0,0 +1,117 @@
1
+ """Changelog and Fragment definitions for Scriv."""
2
+
3
+ import datetime
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ import attr
8
+ import jinja2
9
+
10
+ from .config import Config
11
+ from .format import FormatTools, SectionDict, get_format_tools
12
+ from .util import partition_lines
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @attr.s
18
+ class Fragment:
19
+ """A changelog fragment."""
20
+
21
+ path = attr.ib(type=Path)
22
+ format = attr.ib(type=str, default=None)
23
+ content = attr.ib(type=str, default=None)
24
+
25
+ def __attrs_post_init__(
26
+ self,
27
+ ): # noqa: D105 (Missing docstring in magic method)
28
+ if self.format is None:
29
+ self.format = self.path.suffix.lstrip(".")
30
+
31
+ def write(self) -> None:
32
+ """Write the content to the file."""
33
+ self.path.write_text(self.content)
34
+
35
+ def read(self) -> None:
36
+ """Read the content of the fragment."""
37
+ self.content = self.path.read_text()
38
+
39
+
40
+ @attr.s
41
+ class Changelog:
42
+ """A changelog file."""
43
+
44
+ path = attr.ib(type=Path)
45
+ config = attr.ib(type=Config)
46
+ newline = attr.ib(type=str, default="")
47
+ text_before = attr.ib(type=str, default="")
48
+ changelog = attr.ib(type=str, default="")
49
+ text_after = attr.ib(type=str, default="")
50
+
51
+ def read(self) -> None:
52
+ """Read the changelog if it exists."""
53
+ logger.info(f"Reading changelog {self.path}")
54
+ if self.path.exists():
55
+ with self.path.open("r", encoding="utf-8") as f:
56
+ changelog_text = f.read()
57
+ if f.newlines: # .newlines may be None, str, or tuple
58
+ if isinstance(f.newlines, str):
59
+ self.newline = f.newlines
60
+ else:
61
+ self.newline = f.newlines[0]
62
+ before, marker, after = partition_lines(
63
+ changelog_text, self.config.insert_marker
64
+ )
65
+ if marker:
66
+ self.text_before = before + marker
67
+ rest = after
68
+ else:
69
+ self.text_before = ""
70
+ rest = before
71
+ self.changelog, marker, after = partition_lines(
72
+ rest, self.config.end_marker
73
+ )
74
+ self.text_after = marker + after
75
+ else:
76
+ logger.warning(f"Changelog {self.path} doesn't exist")
77
+
78
+ def format_tools(self) -> FormatTools:
79
+ """Get the appropriate FormatTools for this changelog."""
80
+ return get_format_tools(self.config.format, self.config)
81
+
82
+ def entry_header(self, version, date=None) -> str:
83
+ """Format the header for a new entry."""
84
+ title_data = {
85
+ "date": date or datetime.datetime.now(),
86
+ "version": version,
87
+ }
88
+ title_template = jinja2.Template(self.config.entry_title_template)
89
+ new_title = title_template.render(config=self.config, **title_data)
90
+ if new_title.strip():
91
+ anchor = f"changelog-{version}" if version else None
92
+ new_header = self.format_tools().format_header(
93
+ new_title, anchor=anchor
94
+ )
95
+ else:
96
+ new_header = ""
97
+ return new_header
98
+
99
+ def entry_text(self, sections: SectionDict) -> str:
100
+ """Format the text of a new entry."""
101
+ return self.format_tools().format_sections(sections)
102
+
103
+ def add_entry(self, header: str, text: str) -> None:
104
+ """Add a new entry to the top of the changelog."""
105
+ self.changelog = header + text + self.changelog
106
+
107
+ def write(self) -> None:
108
+ """Write the changelog."""
109
+ f = self.path.open("w", encoding="utf-8", newline=self.newline or None)
110
+ with f:
111
+ f.write(self.text_before)
112
+ f.write(self.changelog)
113
+ f.write(self.text_after)
114
+
115
+ def entries(self) -> SectionDict:
116
+ """Parse the changelog into a SectionDict."""
117
+ return self.format_tools().parse_text(self.changelog)
scriv/cli.py ADDED
@@ -0,0 +1,32 @@
1
+ """Scriv command-line interface."""
2
+
3
+ import logging
4
+
5
+ import click
6
+ import click_log
7
+
8
+ from . import __version__
9
+ from .collect import collect
10
+ from .create import create
11
+ from .ghrel import github_release
12
+ from .print import print_
13
+
14
+ click_log.basic_config(logging.getLogger())
15
+
16
+
17
+ @click.group(
18
+ help=f"""\
19
+ Manage changelogs.
20
+
21
+ Version {__version__}
22
+ """
23
+ )
24
+ @click.version_option()
25
+ def cli() -> None: # noqa: D401
26
+ """The main entry point for the scriv command."""
27
+
28
+
29
+ cli.add_command(create)
30
+ cli.add_command(collect)
31
+ cli.add_command(github_release)
32
+ cli.add_command(print_)
scriv/collect.py ADDED
@@ -0,0 +1,104 @@
1
+ """Collecting fragments."""
2
+
3
+ import logging
4
+ import sys
5
+ from typing import Optional
6
+
7
+ import click
8
+
9
+ from .gitinfo import git_add, git_config_bool, git_edit, git_rm
10
+ from .scriv import Scriv
11
+ from .util import Version, scriv_command
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @click.command()
17
+ @click.option(
18
+ "--add/--no-add",
19
+ default=None,
20
+ help="'git add' the updated changelog file and removed fragments.",
21
+ )
22
+ @click.option(
23
+ "--edit/--no-edit",
24
+ default=None,
25
+ help="Open the changelog file in your text editor.",
26
+ )
27
+ @click.option(
28
+ "--title", default=None, help="The title text to use for this entry."
29
+ )
30
+ @click.option(
31
+ "--keep", is_flag=True, help="Keep the fragment files that are collected."
32
+ )
33
+ @click.option(
34
+ "--version", default=None, help="The version name to use for this entry."
35
+ )
36
+ @scriv_command
37
+ def collect(
38
+ add: Optional[bool],
39
+ edit: Optional[bool],
40
+ title: str,
41
+ keep: bool,
42
+ version: str,
43
+ ) -> None:
44
+ """
45
+ Collect and combine fragments into the changelog.
46
+ """
47
+ if title is not None and version is not None:
48
+ sys.exit("Can't provide both --title and --version.")
49
+
50
+ if add is None:
51
+ add = git_config_bool("scriv.collect.add")
52
+ if edit is None:
53
+ edit = git_config_bool("scriv.collect.edit")
54
+
55
+ scriv = Scriv()
56
+ logger.info(f"Collecting from {scriv.config.fragment_directory}")
57
+ frags = scriv.fragments_to_combine()
58
+ if not frags:
59
+ logger.info("No changelog fragments to collect")
60
+ sys.exit(2)
61
+
62
+ changelog = scriv.changelog()
63
+ changelog.read()
64
+
65
+ if title is None:
66
+ version = Version(version or scriv.config.version)
67
+ if version:
68
+ # Check that we haven't used this version before.
69
+ for etitle in changelog.entries().keys():
70
+ if etitle is None:
71
+ continue
72
+ eversion = Version.from_text(etitle)
73
+ if eversion is None:
74
+ sys.exit(
75
+ f"Entry {etitle!r} is not a valid version! "
76
+ + "If scriv should ignore this heading, add "
77
+ + "'scriv-end-here' somewhere before it."
78
+ )
79
+ if eversion == version:
80
+ sys.exit(
81
+ f"Entry {etitle!r} already uses "
82
+ + f"version {str(version)!r}."
83
+ )
84
+ new_header = changelog.entry_header(version=version)
85
+ else:
86
+ new_header = changelog.format_tools().format_header(title)
87
+
88
+ new_text = changelog.entry_text(scriv.combine_fragments(frags))
89
+ changelog.add_entry(new_header, new_text)
90
+ changelog.write()
91
+
92
+ if edit:
93
+ git_edit(changelog.path)
94
+
95
+ if add:
96
+ git_add(changelog.path)
97
+
98
+ if not keep:
99
+ for frag in frags:
100
+ logger.info(f"Deleting fragment file {str(frag.path)!r}")
101
+ if add:
102
+ git_rm(frag.path)
103
+ else:
104
+ frag.path.unlink()