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 +5 -0
- scriv/__main__.py +5 -0
- scriv/changelog.py +117 -0
- scriv/cli.py +32 -0
- scriv/collect.py +104 -0
- scriv/config.py +449 -0
- scriv/create.py +59 -0
- scriv/exceptions.py +5 -0
- scriv/format.py +76 -0
- scriv/format_md.py +99 -0
- scriv/format_rst.py +168 -0
- scriv/ghrel.py +151 -0
- scriv/github.py +110 -0
- scriv/gitinfo.py +100 -0
- scriv/linkcheck.py +50 -0
- scriv/literals.py +125 -0
- scriv/optional.py +26 -0
- scriv/print.py +74 -0
- scriv/scriv.py +113 -0
- scriv/shell.py +67 -0
- scriv/templates/new_fragment.md.j2 +15 -0
- scriv/templates/new_fragment.rst.j2 +16 -0
- scriv/util.py +138 -0
- scriv-1.6.0.dist-info/METADATA +809 -0
- scriv-1.6.0.dist-info/RECORD +29 -0
- scriv-1.6.0.dist-info/WHEEL +5 -0
- scriv-1.6.0.dist-info/entry_points.txt +2 -0
- scriv-1.6.0.dist-info/licenses/LICENSE.txt +180 -0
- scriv-1.6.0.dist-info/top_level.txt +1 -0
scriv/__init__.py
ADDED
scriv/__main__.py
ADDED
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()
|