Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce snippets #3

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# all history is needed to crawl it properly
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
with:
Expand All @@ -39,8 +42,11 @@ jobs:
echo "latest_version=$latest_version" >> $GITHUB_ENV
- name: Build package
run: |
poetry run changelog-generator \
changelog changelog.md \
--snippets=.snippets
poetry run changelog2version \
--changelog_file changelog.md \
--changelog_file changelog.md.new \
--version_file snippets2changelog/version.py \
--version_file_type py \
--debug
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/test-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# all history is needed to crawl it properly
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
with:
Expand All @@ -36,8 +39,11 @@ jobs:
echo "latest_version=$latest_version" >> $GITHUB_ENV
- name: Build package
run: |
poetry run changelog-generator \
changelog changelog.md \
--snippets=.snippets
poetry run changelog2version \
--changelog_file changelog.md \
--changelog_file changelog.md.new \
--version_file snippets2changelog/version.py \
--version_file_type py \
--additional_version_info="-rc${{ github.run_number }}.dev${{ github.event.number }}" \
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
# all history is needed to crawl it properly
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
with:
Expand All @@ -45,8 +48,11 @@ jobs:
poetry self add "poetry-dynamic-versioning[plugin]"
- name: Build package
run: |
poetry run changelog-generator \
changelog changelog.md \
--snippets=.snippets
poetry run changelog2version \
--changelog_file changelog.md \
--changelog_file changelog.md.new \
--version_file snippets2changelog/version.py \
--version_file_type py \
--debug
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/unittest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ jobs:
test-and-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v3
- name: Checkout
uses: actions/checkout@v3
with:
# all history is needed to crawl it properly
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Execute tests
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# snippets2changelog specific
changelog.md.new

# custom, package specific ignores
.DS_Store
.DS_Store?
Expand Down Expand Up @@ -114,7 +117,7 @@ celerybeat.pid

# Environments
.env
.venv
.venv*
env/
venv/
ENV/
Expand Down
34 changes: 34 additions & 0 deletions .snippets/3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Introduce snippets
<!--
type: breaking
scope: all
affected: all
-->

### Added
- `ChangelogCreator` class to render new changelog from snippets and existing base changelog
- New `changelog-generator changelog` CLI interface available and documented
- `SnippetCollector` to provide iterable of snippets found in specified folder, sorted by the appearance in the Git history (oldest first)
- Template for single changelog entry
- Template for complete rendered and updated changelog

brainelectronics marked this conversation as resolved.
Show resolved Hide resolved
### Changed
#### Breaking
- `SnippetCreator` class has no `file_name` init parameter anymore
- `content` property renamed to `parsed_content`
- `parse` function takes `file_name` parameter
- `parsed_content` gets resetted with every new `parse(file_name)` call
- `_required_keys` renamed to `_required_parser_keys`
- `SnippetCreator` class has no `file_name` and `content` init parameter anymore
- `snippets_file` property removed
- `content` property renamed to `rendered_content`
- `render` function takes `content` parameter and returns `None`
- `create` function takes `file_name` parameter

#### Other
- `changelog2version` moved from dev dependency to package dependency
- Create changelog from snippets in GitHub workflow before creating the python package

### Fixed
- Sorted package dependencies in `pyproject.toml`
- Ignore all `.venv*` directories
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Create version info files based on the latest changelog entry.
- [Usage](#usage)
- [Info](#info)
- [Create](#create)
- [Snippet](#snippet)
- [Changelog](#changelog)
- [Parse](#parse)
- [Contributing](#contributing)
- [Setup](#setup)
Expand All @@ -46,6 +48,7 @@ changelog-generator info
```

### Create
#### Snippet

Create a new snippet with the given name at the specified snippets folder

Expand Down Expand Up @@ -74,6 +77,15 @@ TBD

```

#### Changelog

Create or update a changelog with all snippets.
New changelog will be named `<OLD_CHANGELOG_NAME.new>`

```bash
changelog-generator changelog changelog.md --snippets=.snippets
```

### Parse

Parse an existing snippet file and return the data as JSON without indentation
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ format-jinja = """{{ check_output(["python3", "-c", "from pathlib import Path; e
changelog-generator = 'snippets2changelog.cli:main'

[tool.poetry.dependencies]
python = "^3.11"
pyyaml = "~6.0"
changelog2version = "^0.10"
GitPython = "~3.1.43"
jinja2 = "^3.1.4"
python = "^3.11"
pyyaml = "~6.0"

[tool.poetry.group.dev.dependencies]
black = "*"
changelog2version = "^0.10"
flake8 = "*"
isort = "*"
mypy = "*"
Expand Down
36 changes: 29 additions & 7 deletions snippets2changelog/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import Sequence

from .common import LOG_LEVELS, collect_user_choice
from .creator import SnippetCreator
from .creator import ChangelogCreator, SnippetCreator
from .parser import SnippetParser

LOGGER_FORMAT = '[%(asctime)s] [%(levelname)-8s] [%(filename)-15s @'\
Expand Down Expand Up @@ -50,6 +50,22 @@ def parse_args(argv: Sequence[str] | None = None) -> Args:
)
parser_info.set_defaults(func=fn_info)

parser_changelog = subparsers.add_parser(
"changelog",
help="Create a changelog",
)
parser_changelog.set_defaults(func=fn_changelog)
parser_changelog.add_argument(
"changelog",
type=Path,
help="Path to existing changelog",
)
parser_changelog.add_argument(
"--snippets",
type=lambda x: does_exist(parser, x),
help="Directory to crawl for snippets",
)

parser_create = subparsers.add_parser(
"create",
help="Create a snippet",
Expand Down Expand Up @@ -97,6 +113,11 @@ def fn_info(_args: Args) -> None:
print(f"Version: {extract_version()}")


def fn_changelog(args: Args) -> None:
cc = ChangelogCreator(changelog=args.changelog, snippets_folder=args.snippets, verbosity=args.verbose)
cc.update_changelog()


def fn_create(args: Args) -> None:
content = {
"short_description": input("Short description: "),
Expand All @@ -107,15 +128,16 @@ def fn_create(args: Args) -> None:
"affected": input("Affected users (default all): ") or "all",
"content": "TBD",
}
sc = SnippetCreator(file_name=args.name, content=content)
logger.debug(f"rendered content: >>>>>>\n{sc.render()}\n<<<<<<")
sc.create()
sc = SnippetCreator()
sc.render(content=content)
logger.debug(f"rendered content: >>>>>>\n{sc.rendered_content}\n<<<<<<")
sc.create(file_name=args.name)


def fn_parse(args: Args) -> None:
sp = SnippetParser(file_name=args.name, verbosity=args.verbose)
sp.parse()
print(json.dumps(sp.content, indent=args.indent))
sp = SnippetParser(verbosity=args.verbose)
sp.parse(file_name=args.name)
print(json.dumps(sp.parsed_content, indent=args.indent))


def main() -> int:
Expand Down
118 changes: 118 additions & 0 deletions snippets2changelog/collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3

"""Snippets collector"""

from collections.abc import Iterator
from pathlib import Path

from git import Commit, GitCmdObjectDB, Repo, Submodule, TagReference
from git.refs.head import Head


class CollectorError(Exception):
"""Base class for exceptions in this module."""

pass


class HistoryWalker(object):
"""docstring for HistoryWalker"""

def __init__(
self, repo: Path, search_parent_directories: bool = True, branch_only: bool = True
) -> None:
if repo.exists():
try:
self._repo = Repo(repo, search_parent_directories=search_parent_directories)
except Exception as e:
raise CollectorError(e)
else:
raise CollectorError(f"Given repo folder '{repo}' does not exist")

# limit amount of commits to crawl to a positive number
self._max_count = None
self._branch_only = branch_only

@property
def repo_root(self) -> Path:
return Path(self._repo.working_dir)

@property
def branch_name(self) -> str:
try:
return str(self._repo.active_branch)
except Exception as e:
print(f"HEAD is detached: {e}")
return str(self._repo.head.commit.hexsha)

@property
def tags(self) -> list[TagReference]:
return sorted(self._repo.tags, key=lambda t: t.commit.committed_datetime)

def commits(self) -> Iterator[Commit]:
kwargs = dict()

if self._branch_only:
# Collect the commits on this branch only
# aka ignore commits on other branches
kwargs["first-parent"] = True

# latest commit is first element
for ele in self._repo.iter_commits(
rev=self.branch_name,
max_count=self._max_count,
**kwargs,
):
yield ele


class SnippetCollector(HistoryWalker):
"""docstring for SnippetCollector"""

def __init__(self, snippets_folder: Path, file_extension: str = "md", **kwargs) -> None:
if snippets_folder.exists():
self._snippets_folder = snippets_folder
else:
raise CollectorError(f"Given snippets folder '{snippets_folder}' does not exist")

HistoryWalker.__init__(self, repo=self._snippets_folder, **kwargs)
self._file_extension = file_extension

@property
def snippets_folder(self) -> Path:
return self._snippets_folder

def all_snippet_files(self) -> Iterator[Path]:
"""Get all potential snippet files from the snippets folder"""
for file in self._snippets_folder.iterdir():
if file.is_file() and (file.suffix == ".{}".format(self._file_extension)):
yield file

def snippets(self) -> Iterator[tuple[Commit, Path]]:
collected_snippets = list(self.all_snippet_files())

# nice chaos :)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be fixed, but not today, it's already tomorrow ... 😩

# close to midnight, stop now or the problem of tomorrow will catch you

# self._logger.debug(f"Repo root: {self.repo_root}")
# changelog-generator/

# self._logger.debug(f"collected_snippets: {collected_snippets}, looking for {self.snippets_folder}")
# collected_snippets: [PosixPath('.snippets/3.md')], looking for .snippets

# use reversed to have oldest commit as first element
for idx, commit in reversed(list(enumerate(self.commits()))):
for file in commit.stats.files.keys():
# self._logger.debug(f"{idx}: {commit}, looking for file: {file} in {collected_snippets}")
# 0: b768d6983432b730d81b34d125a4bbefb0a66525, looking for file: .snippets/3.md in [PosixPath('.snippets/3.md')]
# self._logger.debug(Path(file) in collected_snippets)
# True
"""
if self.snippets_folder / file in collected_snippets:
self._logger.warning(f"file {self.snippets_folder / file} is a match")
yield (commit, self.snippets_folder / file)
"""
if Path(file) in collected_snippets:
# self._logger.debug(f"file {file} is a match")
# file .snippets/3.md is a match
yield (commit, Path(file))
Loading
Loading