mirror of
https://github.com/drewcassidy/yaclog.git
synced 2024-09-01 14:58:58 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
9b0ae90ee2 | |||
15e4d691f5 | |||
9a7e3da60d | |||
94f692e6c5 | |||
c7583388c6 | |||
fe9aa937d2 | |||
caa4560d6d | |||
03841ad07e | |||
aa2390312a | |||
07d76cdc09 | |||
8c79e158c8 | |||
21defeffce | |||
dccde1909b | |||
c25b780772 | |||
bf2e8f670f | |||
dc90731f3d | |||
5a6cb51d71 | |||
52fc36ab70 | |||
c696071b8f | |||
b0419dad80 | |||
2bfaa78053 | |||
524a1da4c6 | |||
acedf2b401 | |||
21b530c256 |
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Set update schedule for GitHub Actions
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every weekday
|
||||
interval: "daily"
|
14
.github/workflows/python-publish.yml
vendored
14
.github/workflows/python-publish.yml
vendored
@ -9,13 +9,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ 3.8, 3.9 ]
|
||||
python-version: [ "3.8", "3.9", "3.10" ]
|
||||
click-version: [ "click~=7.0", "click~=8.0" ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@ -23,6 +24,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8
|
||||
python -m pip install ${{ matrix.click-version }}
|
||||
|
||||
- name: Install module
|
||||
run: python -m pip install .
|
||||
@ -43,10 +45,10 @@ jobs:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3.1.1
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
@ -64,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Get version name and body
|
||||
run: |
|
||||
echo "VERSION_TILE=Version $(yaclog show -n)" >> $GITHUB_ENV
|
||||
echo "VERSION_TILE=$(yaclog show -n)" >> $GITHUB_ENV
|
||||
echo "$(yaclog show -mb)" >> RELEASE.md
|
||||
|
||||
- name: Publish to PyPI
|
||||
|
41
CHANGELOG.md
41
CHANGELOG.md
@ -2,7 +2,34 @@
|
||||
|
||||
All notable changes to this project will be documented in this file
|
||||
|
||||
## 1.0.1 - 2021-05-10
|
||||
## Version 1.0.4 - 2022-04-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed tests folder being installed as a package
|
||||
|
||||
|
||||
## Version 1.0.3 - 2021-05-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `show` command not working with Click version 8
|
||||
- Fixed release message incorrectly stating if a commit will be created or not
|
||||
|
||||
|
||||
## Version 1.0.2 - 2021-05-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to support Click version 8
|
||||
- Modified module documentation page titles to include a module role
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed tag names with spaces in versions
|
||||
|
||||
|
||||
## Version 1.0.1 - 2021-05-10
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -10,7 +37,7 @@ All notable changes to this project will be documented in this file
|
||||
- Improved consistency in command documentation metavars
|
||||
|
||||
|
||||
## 1.0.0 - 2021-05-07
|
||||
## Version 1.0.0 - 2021-05-07
|
||||
|
||||
### Changed
|
||||
|
||||
@ -33,7 +60,7 @@ All notable changes to this project will be documented in this file
|
||||
- Extra newlines are added between versions to improve readability of the raw markdown file.
|
||||
|
||||
|
||||
## 0.3.3 - 2021-04-27
|
||||
## Version 0.3.3 - 2021-04-27
|
||||
|
||||
### Added
|
||||
|
||||
@ -48,7 +75,7 @@ All notable changes to this project will be documented in this file
|
||||
- `release` now works with logs that have only unreleased changes
|
||||
|
||||
|
||||
## 0.3.2 - 2021-04-24
|
||||
## Version 0.3.2 - 2021-04-24
|
||||
|
||||
### Added
|
||||
|
||||
@ -64,7 +91,7 @@ All notable changes to this project will be documented in this file
|
||||
- `release` and `entry` commands now work using empty changelogs.
|
||||
|
||||
|
||||
## 0.3.1 - 2021-04-24
|
||||
## Version 0.3.1 - 2021-04-24
|
||||
|
||||
### Added
|
||||
|
||||
@ -77,7 +104,7 @@ All notable changes to this project will be documented in this file
|
||||
- `release` command for creating releases
|
||||
|
||||
|
||||
## 0.2.0 - 2021-04-19
|
||||
## Version 0.2.0 - 2021-04-19
|
||||
|
||||
### Added
|
||||
|
||||
@ -91,7 +118,7 @@ All notable changes to this project will be documented in this file
|
||||
- Parser can now handle setext-style headers and H2s not conforming to the schema.
|
||||
|
||||
|
||||
## 0.1.0 - 2021-04-16
|
||||
## Version 0.1.0 - 2021-04-16
|
||||
|
||||
First release
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
Changelog Module
|
||||
================
|
||||
:py:mod:`changelog` Module
|
||||
==========================
|
||||
|
||||
.. automodule:: yaclog.changelog
|
||||
:members:
|
@ -1,5 +1,5 @@
|
||||
Markdown Module
|
||||
===============
|
||||
:py:mod:`markdown` Module
|
||||
=========================
|
||||
|
||||
.. automodule:: yaclog.markdown
|
||||
:members:
|
@ -1,5 +1,5 @@
|
||||
Version Module
|
||||
==============
|
||||
:py:mod:`version` Module
|
||||
========================
|
||||
|
||||
.. automodule:: yaclog.version
|
||||
:members:
|
@ -1,9 +1,58 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools >= 35.0.2",
|
||||
"setuptools_scm[toml] >= 3.4",
|
||||
"setuptools>=61",
|
||||
"setuptools_scm>=6.2",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
[project]
|
||||
name = "yaclog"
|
||||
description = "Yet another changelog CLI tool."
|
||||
readme = "README.md"
|
||||
license = { file = "LICENSE.md" }
|
||||
authors = [{ name = "Andrew Cassidy", email = "drewcassidy@me.com" }]
|
||||
keywords = ["changelog", "commandline", "markdown"]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Affero General Public License v3",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Topic :: Text Processing :: Markup :: Markdown",
|
||||
"Topic :: Software Development :: Version Control :: Git",
|
||||
"Topic :: Utilities"
|
||||
]
|
||||
|
||||
requires-python = ">= 3.8"
|
||||
dependencies = [
|
||||
"Click >= 7.0",
|
||||
"GitPython >= 3",
|
||||
"packaging >= 20"
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
"Sphinx >= 3.5",
|
||||
"sphinx-click >= 2.7",
|
||||
"sphinx-rtd-theme",
|
||||
"myst-parser >= 0.14",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
yaclog = "yaclog.cli.__main__:cli"
|
||||
|
||||
[project.urls]
|
||||
Source = "https://github.com/drewcassidy/yaclog"
|
||||
Changelog = "https://github.com/drewcassidy/yaclog/blob/main/CHANGELOG.md"
|
||||
Docs = "https://yaclog.readthedocs.io/"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["yaclog"]
|
50
setup.cfg
50
setup.cfg
@ -1,50 +0,0 @@
|
||||
[metadata]
|
||||
# until setuptools supports PEP621, this will have to do
|
||||
name = yaclog
|
||||
description = Yet another changelog CLI tool.
|
||||
author = Andrew Cassidy
|
||||
license = AGPLv3
|
||||
license_file = LICENSE.md
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
||||
keywords = changelog, commandline, markdown
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: GNU Affero General Public License v3
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Topic :: Text Processing :: Markup :: Markdown
|
||||
Topic :: Software Development :: Version Control :: Git
|
||||
Topic :: Utilities
|
||||
|
||||
project_urls =
|
||||
Source = https://github.com/drewcassidy/yaclog
|
||||
Changelog = https://github.com/drewcassidy/yaclog/blob/main/CHANGELOG.md
|
||||
Docs = https://yaclog.readthedocs.io/
|
||||
|
||||
[options]
|
||||
install_requires =
|
||||
Click ~= 7.0
|
||||
GitPython >= 3
|
||||
packaging >= 20
|
||||
python_requires = >= 3.8
|
||||
packages = find:
|
||||
|
||||
[options.extras_require]
|
||||
docs =
|
||||
Sphinx >= 3.5
|
||||
sphinx-click >= 2.7
|
||||
sphinx-rtd-theme
|
||||
myst-parser >= 0.14
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
yaclog = yaclog.cli.__main__:cli
|
||||
|
||||
[options.packages.find]
|
||||
exclude = tests.*
|
@ -210,7 +210,7 @@ class TestRelease(unittest.TestCase):
|
||||
runner.invoke(cli, ['init']) # create the changelog
|
||||
runner.invoke(cli, ['entry', '-b', 'entry number 1'])
|
||||
|
||||
result = runner.invoke(cli, ['release', '1.0.0', '-c'], input='y\n')
|
||||
result = runner.invoke(cli, ['release', 'Version 1.0.0', '-c'], input='y\n')
|
||||
check_result(self, result)
|
||||
self.assertIn('Created commit', result.output)
|
||||
self.assertIn('Created tag', result.output)
|
||||
@ -218,5 +218,70 @@ class TestRelease(unittest.TestCase):
|
||||
self.assertEqual(repo.tags[0].name, '1.0.0')
|
||||
|
||||
|
||||
class TestShow(unittest.TestCase):
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
def setUp(self):
|
||||
self.runner = CliRunner()
|
||||
self.location = 'CHANGELOG.md'
|
||||
|
||||
self.log = yaclog.Changelog()
|
||||
|
||||
self.log.add_version(name='1.0.0').add_entry('- entry number 1')
|
||||
self.log.add_version(name='Version 2.0.0').add_entry('- entry number 2', 'Added')
|
||||
self.log.add_version(name='Three Point Oh').add_entry('entry number 3')
|
||||
v = self.log.add_version(name='4.0.0 "Euclid"')
|
||||
v.add_entry('- entry number 4')
|
||||
v.add_entry('- entry number 5')
|
||||
v.tags.append('TAGGED')
|
||||
|
||||
self.modes = {
|
||||
'full': ([], lambda v, k: v.text(**k), '\n\n'),
|
||||
'name': (['-n'], lambda v, k: v.name, '\n'),
|
||||
'body': (['-b'], lambda v, k: v.body(**k), '\n\n'),
|
||||
'header': (['-h'], lambda v, k: v.header(**k), '\n'),
|
||||
}
|
||||
|
||||
def test_show_all(self):
|
||||
"""Test showing all version information"""
|
||||
|
||||
with self.runner.isolated_filesystem():
|
||||
self.log.write(self.location)
|
||||
|
||||
for mode, t in self.modes.items():
|
||||
with self.subTest(mode, flags=t[0]):
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', '-a'] + t[0]))
|
||||
self.assertEqual(t[2].join([t[1](v, {'md': False}) for v in self.log.versions]),
|
||||
result.output.strip(), 'incorrect plaintext output')
|
||||
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', '-am'] + t[0]))
|
||||
self.assertEqual(t[2].join([t[1](v, {'md': True}) for v in self.log.versions]),
|
||||
result.output.strip(), 'incorrect markdown output')
|
||||
|
||||
def test_show_version(self):
|
||||
with self.runner.isolated_filesystem():
|
||||
self.log.write(self.location)
|
||||
|
||||
for mode, t in self.modes.items():
|
||||
with self.subTest(mode, flags=t[0]):
|
||||
|
||||
for version in self.log.versions:
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', version.name] + t[0]))
|
||||
self.assertEqual(t[1](version, {'md': False}),
|
||||
result.output.strip(), 'incorrect plaintext output')
|
||||
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', version.name[-5:]] + t[0]))
|
||||
self.assertEqual(t[1](version, {'md': False}),
|
||||
result.output.strip(), 'incorrect plaintext output')
|
||||
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', version.name, '-m'] + t[0]))
|
||||
self.assertEqual(t[1](version, {'md': True}),
|
||||
result.output.strip(), 'incorrect markdown output')
|
||||
|
||||
check_result(self, result := self.runner.invoke(cli, ['show', version.name[-5:], '-m'] + t[0]))
|
||||
self.assertEqual(t[1](version, {'md': True}),
|
||||
result.output.strip(), 'incorrect markdown output')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -381,14 +381,15 @@ class Changelog:
|
||||
|
||||
def get_version(self, name: Optional[str] = None) -> VersionEntry:
|
||||
"""
|
||||
Get a version from the changelog by name
|
||||
Get a version from the changelog by name.
|
||||
|
||||
:param name: The name of the version to get, or `None` to return the most recent
|
||||
:param name: The name of the version to get, or `None` to return the most recent.
|
||||
The first version with this value in its name is returned.
|
||||
:return: The first version with the selected name
|
||||
"""
|
||||
|
||||
for version in self.versions:
|
||||
if version.name == name or name is None:
|
||||
if name in version.name or name is None:
|
||||
return version
|
||||
raise KeyError(f'Version {name} not found in changelog')
|
||||
|
||||
|
@ -59,24 +59,36 @@ def reformat(obj: Changelog):
|
||||
click.echo(f'Reformatted changelog file at {obj.path}')
|
||||
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
@cli.command(short_help='Show changes from the changelog file')
|
||||
@click.option('--all', '-a', 'all_versions', is_flag=True, help='Show the entire changelog.')
|
||||
@click.option('--markdown/--txt', '-m/-t', default=False, help='Display as markdown or plain text.')
|
||||
@click.option('--full', '-f', 'str_func', flag_value=lambda v, k: v.text(**k), default=True,
|
||||
@click.option('--full', '-f', 'mode', flag_value='full', default=True,
|
||||
help='Show version header and body.')
|
||||
@click.option('--name', '-n', 'str_func', flag_value=lambda v, k: v.name, help='Show only the version name')
|
||||
@click.option('--body', '-b', 'str_func', flag_value=lambda v, k: v.body(**k), help='Show only the version body.')
|
||||
@click.option('--header', '-h', 'str_func', flag_value=lambda v, k: v.preamble(**k),
|
||||
@click.option('--name', '-n', 'mode', flag_value='name',
|
||||
help='Show only the version name')
|
||||
@click.option('--body', '-b', 'mode', flag_value='body',
|
||||
help='Show only the version body.')
|
||||
@click.option('--header', '-h', 'mode', flag_value='header',
|
||||
help='Show only the version header.')
|
||||
@click.argument('version_names', metavar='VERSIONS', type=str, nargs=-1)
|
||||
@click.pass_obj
|
||||
def show(obj: Changelog, all_versions, markdown, str_func, version_names):
|
||||
def show(obj: Changelog, all_versions, markdown, mode, version_names):
|
||||
"""
|
||||
Show the changes for VERSIONS.
|
||||
|
||||
VERSIONS is a list of versions to print. If not given, the most recent version is used.
|
||||
"""
|
||||
|
||||
functions = {
|
||||
'full': (lambda v, k: v.text(**k)),
|
||||
'name': (lambda v, k: v.name),
|
||||
'body': (lambda v, k: v.body(**k)),
|
||||
'header': (lambda v, k: v.header(**k)),
|
||||
}
|
||||
|
||||
str_func = functions[mode]
|
||||
|
||||
try:
|
||||
if all_versions:
|
||||
versions = obj.versions
|
||||
@ -85,16 +97,14 @@ def show(obj: Changelog, all_versions, markdown, str_func, version_names):
|
||||
else:
|
||||
versions = [obj.get_version(name) for name in version_names]
|
||||
except KeyError as k:
|
||||
raise click.BadArgumentUsage(k)
|
||||
raise click.BadArgumentUsage(str(k))
|
||||
except ValueError as v:
|
||||
raise click.ClickException(v)
|
||||
raise click.ClickException(str(v))
|
||||
|
||||
kwargs = {'md': markdown, 'color': True}
|
||||
|
||||
for v in versions:
|
||||
text = str_func(v, kwargs)
|
||||
click.echo(text)
|
||||
click.echo('\n')
|
||||
sep = '\n\n' if mode == 'body' or mode == 'full' else '\n'
|
||||
click.echo(sep.join([str_func(v, kwargs) for v in versions]))
|
||||
|
||||
|
||||
@cli.command(short_help='Modify version tags')
|
||||
@ -115,9 +125,9 @@ def tag(obj: Changelog, add, tag_name: str, version_name: str):
|
||||
else:
|
||||
version = obj.current_version()
|
||||
except KeyError as k:
|
||||
raise click.BadArgumentUsage(k)
|
||||
raise click.BadArgumentUsage(str(k))
|
||||
except ValueError as v:
|
||||
raise click.ClickException(v)
|
||||
raise click.ClickException(str(v))
|
||||
|
||||
if add:
|
||||
version.tags.append(tag_name)
|
||||
@ -153,7 +163,7 @@ def entry(obj: Changelog, bullets, paragraphs, section_name, version_name):
|
||||
else:
|
||||
version = obj.current_version(released=False, new_version=True)
|
||||
except KeyError as k:
|
||||
raise click.BadArgumentUsage(k)
|
||||
raise click.BadArgumentUsage(str(k))
|
||||
|
||||
for p in paragraphs:
|
||||
version.add_entry(p, section_name)
|
||||
@ -172,13 +182,20 @@ def entry(obj: Changelog, bullets, paragraphs, section_name, version_name):
|
||||
|
||||
|
||||
@cli.command(short_help='Release versions.')
|
||||
@click.option('-M', '--major', 'rel_seg', flag_value=0, default=None, help='Increment major version number.')
|
||||
@click.option('-m', '--minor', 'rel_seg', flag_value=1, help='Increment minor version number.')
|
||||
@click.option('-p', '--patch', 'rel_seg', flag_value=2, help='Increment patch number.')
|
||||
@click.option('-a', '--alpha', 'pre_seg', flag_value='a', default=None, help='Increment alpha version number.')
|
||||
@click.option('-b', '--beta', 'pre_seg', flag_value='b', help='Increment beta version number.')
|
||||
@click.option('-r', '--rc', 'pre_seg', flag_value='rc', help='Increment release candidate version number.')
|
||||
@click.option('-f', '--full', 'pre_seg', flag_value='', help='Clear the prerelease value creating a full release.')
|
||||
@click.option('-M', '--major', 'rel_seg', flag_value=0, type=int, default=None,
|
||||
help='Increment major version number.')
|
||||
@click.option('-m', '--minor', 'rel_seg', flag_value=1, type=int,
|
||||
help='Increment minor version number.')
|
||||
@click.option('-p', '--patch', 'rel_seg', flag_value=2, type=int,
|
||||
help='Increment patch number.')
|
||||
@click.option('-a', '--alpha', 'pre_seg', flag_value='a', type=str, default=None,
|
||||
help='Increment alpha version number.')
|
||||
@click.option('-b', '--beta', 'pre_seg', flag_value='b', type=str,
|
||||
help='Increment beta version number.')
|
||||
@click.option('-r', '--rc', 'pre_seg', flag_value='rc', type=str,
|
||||
help='Increment release candidate version number.')
|
||||
@click.option('-f', '--full', 'pre_seg', flag_value='',
|
||||
help='Clear the prerelease value creating a full release.')
|
||||
@click.option('-c', '--commit', is_flag=True,
|
||||
help='Create a git commit tagged with the new version number. '
|
||||
'If there are no changes to commit, the current commit will be tagged instead.')
|
||||
@ -240,7 +257,7 @@ def release(obj: Changelog, version_name, rel_seg, pre_seg, commit):
|
||||
tracked = len(repo.index.diff(repo.head.commit))
|
||||
untracked = len(repo.index.diff(None))
|
||||
|
||||
message = [['Commit and create tag', 'Create tag'][min(tracked, 1)], 'for']
|
||||
message = [['Create tag', 'Commit and create tag'][min(tracked, 1)], 'for']
|
||||
|
||||
if not cur_version.released:
|
||||
message.append('non-release')
|
||||
@ -255,12 +272,16 @@ def release(obj: Changelog, version_name, rel_seg, pre_seg, commit):
|
||||
click.confirm(' '.join(message), abort=True)
|
||||
|
||||
if tracked > 0:
|
||||
commit = repo.index.commit(f'Version {cur_version.name}\n\n{cur_version.body()}')
|
||||
commit = repo.index.commit(f'Release {cur_version.name}\n\n{cur_version.body()}')
|
||||
click.echo(f"Created commit {click.style(repo.head.commit.hexsha[0:7], fg='green')}")
|
||||
else:
|
||||
commit = repo.head.commit
|
||||
|
||||
repo_tag = repo.create_tag(cur_version.name, ref=commit, message=cur_version.body(False))
|
||||
short_version, *_ = yaclog.version.extract_version(cur_version.name)
|
||||
if not short_version:
|
||||
short_version = cur_version.name.replace(' ', '-')
|
||||
|
||||
repo_tag = repo.create_tag(short_version, ref=commit, message=cur_version.body(False))
|
||||
click.echo(f"Created tag {click.style(repo_tag.name, fg='green')}.")
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user