Skip to content

Commit

Permalink
Create API classes
Browse files Browse the repository at this point in the history
The scriv.scriv module has Scriv, Changelog, and Fragment classes.
  • Loading branch information
Ned Batchelder committed Jun 20, 2021
1 parent 2560e73 commit fd5a48d
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 205 deletions.
5 changes: 5 additions & 0 deletions changelog.d/20210620_190043_nedbat_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added
.....

- A new poorly documented API is available. See the Scriv, Changelog, and
Fragment classes in the scriv.scriv module.
142 changes: 16 additions & 126 deletions src/scriv/collect.py
Original file line number Diff line number Diff line change
@@ -1,99 +1,17 @@
"""Collecting fragments."""

import collections
import datetime
import itertools
import logging
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TypeVar
from typing import Optional

import click
import click_log
import jinja2

from .config import Config
from .format import SectionDict, get_format_tools
from .gitinfo import git_add, git_config_bool, git_edit, git_rm
from .scriv import Scriv

logger = logging.getLogger()


def files_to_combine(config: Config) -> List[Path]:
"""
Find all the files to be combined.
The files are returned in the order they should be processed.
"""
return sorted(
itertools.chain.from_iterable(
[
Path(config.fragment_directory).glob(pattern)
for pattern in ["*.rst", "*.md"]
]
)
)


def sections_from_file(config: Config, filename: Path) -> SectionDict:
"""
Collect the sections from a file.
"""
format_tools = get_format_tools(filename.suffix.lstrip("."), config)
text = filename.read_text().rstrip()
file_sections = format_tools.parse_text(text)
return file_sections


def combine_sections(config: Config, files: Iterable[Path]) -> SectionDict:
"""
Read files, and produce a combined SectionDict of their contents.
"""
sections = collections.defaultdict(list) # type: SectionDict
for file in files:
file_sections = sections_from_file(config, file)
for section, paragraphs in file_sections.items():
sections[section].extend(paragraphs)
return sections


T = TypeVar("T")
K = TypeVar("K")


def order_dict(
d: Dict[Optional[K], T], keys: Sequence[Optional[K]]
) -> Dict[Optional[K], T]:
"""
Produce an OrderedDict of `d`, but with the keys in `keys` order.
"""
with_order = collections.OrderedDict()
to_insert = set(d)
for k in keys:
if k not in to_insert:
continue
with_order[k] = d[k]
to_insert.remove(k)

for k in to_insert:
with_order[k] = d[k]

return with_order


def cut_at_line(text: str, marker: str) -> Tuple[str, str]:
"""
Split text into two parts: up to the line with marker, and lines after.
If `marker` isn't in the text, return ("", text)
"""
lines = text.splitlines(keepends=True)
for i, line in enumerate(lines):
if marker in line:
return "".join(lines[: i + 1]), "".join(lines[i + 1 :])
return ("", text)


@click.command()
@click.option(
"--add/--no-add", default=None, help="'git add' the updated changelog file."
Expand Down Expand Up @@ -121,55 +39,27 @@ def collect(
if edit is None:
edit = git_config_bool("scriv.collect.edit")

config = Config.read()
logger.info("Collecting from {}".format(config.fragment_directory))
files = files_to_combine(config)
sections = combine_sections(config, files)
sections = order_dict(sections, [None] + config.categories)
scriv = Scriv()
logger.info("Collecting from {}".format(scriv.config.fragment_directory))
frags = scriv.fragments_to_combine()

changelog = Path(config.output_file)
newline = ""
if changelog.exists():
with changelog.open("r") as f:
changelog_text = f.read()
if f.newlines: # .newlines may be None, str, or tuple
if isinstance(f.newlines, str):
newline = f.newlines
else:
newline = f.newlines[0]
text_before, text_after = cut_at_line(
changelog_text, config.insert_marker
)
else:
text_before = ""
text_after = ""
changelog = scriv.changelog()
changelog.read()

format_tools = get_format_tools(config.format, config)
title_data = {
"date": datetime.datetime.now(),
"version": version or config.version,
}
new_title = jinja2.Template(config.entry_title_template).render(
config=config, **title_data
)
if new_title.strip():
new_header = format_tools.format_header(new_title)
else:
new_header = ""
new_text = format_tools.format_sections(sections)
with changelog.open("w", newline=newline or None) as f:
f.write(text_before + new_header + new_text + text_after)
new_header = changelog.entry_header(version=version)
new_text = changelog.entry_text(scriv.combine_fragments(frags))
changelog.write(new_header, new_text)

if edit:
git_edit(changelog)
git_edit(changelog.path)

if add:
git_add(changelog)
git_add(changelog.path)

if not keep:
for file in files:
logger.info("Deleting fragment file {}".format(file))
for frag in frags:
logger.info("Deleting fragment file {!r}".format(str(frag.path)))
if add:
git_rm(file)
git_rm(frag.path)
else:
file.unlink()
frag.path.unlink()
53 changes: 9 additions & 44 deletions src/scriv/create.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,18 @@
"""Creating fragments."""

import datetime
import logging
import re
import sys
import textwrap
from pathlib import Path
from typing import Optional

import click
import click_log
import jinja2

from .collect import sections_from_file
from .config import Config
from .gitinfo import (
current_branch_name,
git_add,
git_config_bool,
git_edit,
user_nick,
)
from .gitinfo import git_add, git_config_bool, git_edit
from .scriv import Scriv

logger = logging.getLogger()


def new_fragment_path(config: Config) -> Path:
"""
Return the file path for a new fragment.
"""
file_name = "{:%Y%m%d_%H%M%S}_{}".format(
datetime.datetime.now(), user_nick()
)
branch_name = current_branch_name()
if branch_name and branch_name not in config.main_branches:
branch_name = branch_name.rpartition("/")[-1]
branch_name = re.sub(r"[^a-zA-Z0-9_]", "_", branch_name)
file_name += "_{}".format(branch_name)
file_name += ".{}".format(config.format)
file_path = Path(config.fragment_directory) / file_name
return file_path


def new_fragment_contents(config: Config) -> str:
"""Produce the initial contents of a scriv fragment."""
return jinja2.Template(
textwrap.dedent(config.new_fragment_template)
).render(config=config)


@click.command()
@click.option(
"--add/--no-add", default=None, help="'git add' the created file."
Expand All @@ -68,24 +32,25 @@ def create(add: Optional[bool], edit: Optional[bool]) -> None:
if edit is None:
edit = git_config_bool("scriv.create.edit")

config = Config.read()
if not Path(config.fragment_directory).exists():
scriv = Scriv()
frag = scriv.new_fragment()
file_path = frag.path
if not file_path.parent.exists():
sys.exit(
"Output directory {!r} doesn't exist, please create it.".format(
config.fragment_directory
str(file_path.parent)
)
)

file_path = new_fragment_path(config)
if file_path.exists():
sys.exit("File {} already exists, not overwriting".format(file_path))

logger.info("Creating {}".format(file_path))
file_path.write_text(new_fragment_contents(config))
frag.write()

if edit:
git_edit(file_path)
sections = sections_from_file(config, file_path)
sections = scriv.sections_from_fragment(frag)
if not sections:
logger.info("Empty fragment, aborting...")
file_path.unlink()
Expand Down
Loading

0 comments on commit fd5a48d

Please sign in to comment.