Tests! And code formatting.

This commit is contained in:
Dan Helfman 2021-10-22 13:44:56 -07:00
parent 8da5f81f5e
commit 41bedb182b
7 changed files with 129 additions and 37 deletions

View File

@ -3,12 +3,13 @@
import collections
import sys
import tempfile
CHAPTER_MARKER = '## '
STATUS_MARKER = '[status]: # '
ACT_MARKER = '[act]: # '
COMMENT_MARKER = '[//]: # ' # Strandard markdown comment marker, supported by pandoc and calibre's ebook-convert
# Standard markdown comment marker, supported by Pandoc and Calibre's ebook-convert.
COMMENT_MARKER = '[//]: # '
def count_words(line):
@ -31,7 +32,8 @@ def main():
if '-pp' in arguments:
# -pp flag to allow Markdown Preprocessing primarily to allow multi-file novel formatting
# this is implemented using a temporary file created using python's buit-in tempfile library
import MarkdownPP, tempfile
import MarkdownPP
mdfile = tempfile.TemporaryFile(mode='w+')
MarkdownPP.MarkdownPP(input=open(filename), output=mdfile, modules=list(MarkdownPP.modules))
mdfile.seek(0)
@ -52,22 +54,24 @@ def main():
word_count_by_act[act_heading] += word_count_by_chapter[chapter_heading]
total_word_count += word_count_by_chapter[chapter_heading]
chapter_heading = line[len(CHAPTER_MARKER):].strip('()\n')
chapter_heading = line[len(CHAPTER_MARKER) :].strip('()\n')
word_count_by_chapter[chapter_heading] = count_words(chapter_heading) # Count the words in chapter heading, because the chapter number and title count as words.
# Count the words in chapter heading, because the chapter number and title count as words.
word_count_by_chapter[chapter_heading] = count_words(chapter_heading)
status_by_chapter[chapter_heading] = collections.defaultdict(int)
current_status = None
elif line.startswith(STATUS_MARKER): # Modified to allow multiple statuses in a single chapter, can swap back and forth.
if current_status == None:
current_status = line[len(STATUS_MARKER):].strip('()\n')
# Modified to allow multiple statuses in a single chapter, can swap back and forth.
elif line.startswith(STATUS_MARKER):
if current_status is None:
current_status = line[len(STATUS_MARKER) :].strip('()\n')
status_by_chapter[chapter_heading][current_status] = count_words(chapter_heading)
else:
current_status = line[len(STATUS_MARKER):].strip('()\n')
current_status = line[len(STATUS_MARKER) :].strip('()\n')
elif line.startswith(ACT_MARKER):
act_heading = line[len(ACT_MARKER):].strip('()\n')
act_heading = line[len(ACT_MARKER) :].strip('()\n')
word_count_by_act[act_heading] = count_words(act_heading)
elif line.startswith(COMMENT_MARKER): # don't count the words in a comment
elif line.startswith(COMMENT_MARKER): # Don't count the words in a comment.
pass
else:
line_word_count = count_words(line)
@ -83,7 +87,8 @@ def main():
word_count_by_act[act_heading] += word_count_by_chapter[chapter_heading]
total_word_count += word_count_by_chapter[chapter_heading]
if '-c' in arguments or '--chapter' in arguments: # -c or --chapter to give a chapter-by-chapter word count summary
# -c or --chapter to give a chapter-by-chapter word count summary.
if '-c' in arguments or '--chapter' in arguments:
for chapter_heading, chapter_word_count in word_count_by_chapter.items():
if chapter_heading is None:
continue
@ -102,17 +107,24 @@ def main():
print()
if '-a' in arguments or '--act' in arguments: # -a or --act to give an act-by-act word count summary
# -a or --act to give an act-by-act word count summary.
if '-a' in arguments or '--act' in arguments:
for act_heading, act_word_count in word_count_by_act.items():
if act_heading is None:
continue
print('act {}: {:,} words (~{}%)'.format(act_heading, act_word_count, act_word_count * 100 // total_word_count))
print(
'act {}: {:,} words (~{}%)'.format(
act_heading, act_word_count, act_word_count * 100 // total_word_count
)
)
print()
for status, status_word_count in word_count_by_status.items():
print(f'{status}: {status_word_count:,} words (~{status_word_count * 100 // total_word_count}%)')
print(
f'{status}: {status_word_count:,} words (~{status_word_count * 100 // total_word_count}%)'
)
print(f'total: {total_word_count:,} words')

3
pyproject.toml Normal file
View File

@ -0,0 +1,3 @@
[tool.black]
line-length = 100
skip-string-normalization = true

20
setup.cfg Normal file
View File

@ -0,0 +1,20 @@
[metadata]
description_file=README.md
[tool:pytest]
testpaths = tests
addopts = --cov-report term-missing:skip-covered --cov=novel_stats
filterwarnings =
ignore:Coverage disabled.*:pytest.PytestWarning
[flake8]
ignore = E203,E501,W503
exclude = *.*/*
[tool:isort]
force_single_line = False
include_trailing_comma = True
known_first_party = novel_stats
line_length = 100
multi_line_output = 3
skip = .tox

View File

@ -1,33 +1,27 @@
from setuptools import find_packages, setup
VERSION = '0.1.0'
VERSION = "0.1.0"
setup(
name='novel-stats',
name="novel-stats",
version=VERSION,
description='Produce word count statistics for novels written in Markdown format.',
author='Dan Helfman',
author_email='witten@torsion.org',
url='https://projects.torsion.org/witten/novel-stats',
description="Produce word count statistics for novels written in Markdown format.",
author="Dan Helfman",
author_email="witten@torsion.org",
url="https://projects.torsion.org/witten/novel-stats",
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: Other Audience',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python',
'Topic :: Office/Business',
'Topic :: Text Processing :: Markup',
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: Other Audience",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python",
"Topic :: Office/Business",
"Topic :: Text Processing :: Markup",
],
packages=find_packages(exclude=['tests*']),
entry_points={
'console_scripts': [
'novel-stats = novel_stats.novel_stats:main',
]
},
packages=find_packages(exclude=["tests*"]),
entry_points={"console_scripts": ["novel-stats = novel_stats.novel_stats:main",]},
install_requires=(),
extras_require = {
'multi_file': ['MarkdownPP'],
},
extras_require={"multi_file": ["MarkdownPP"],},
include_package_data=True,
)

18
test_requirements.txt Normal file
View File

@ -0,0 +1,18 @@
appdirs==1.4.4; python_version >= '3.8'
attrs==20.3.0; python_version >= '3.8'
black==19.10b0; python_version >= '3.8'
click==7.1.2; python_version >= '3.8'
coverage==5.3
flake8==3.8.4
flexmock==0.10.4
isort==5.9.1
mccabe==0.6.1
pluggy==0.13.1
pathspec==0.8.1; python_version >= '3.8'
py==1.10.0
pycodestyle==2.6.0
pyflakes==2.2.0
pytest==6.1.2
pytest-cov==2.10.1
regex; python_version >= '3.8'
typed-ast==1.4.2; python_version >= '3.8'

17
tests/test_novel_stats.py Normal file
View File

@ -0,0 +1,17 @@
from novel_stats import novel_stats as module
def test_count_words_tallies_basic_sentence():
assert module.count_words('This sentence is five words.') == 5
def test_count_words_em_dash_does_not_split_words():
assert module.count_words('This is only six—or is it?') == 6
def test_count_words_skips_chapter_marker():
assert module.count_words('## Chapter 1') == 2
def test_count_words_skips_scene_break():
assert module.count_words('* * *') == 0

28
tox.ini Normal file
View File

@ -0,0 +1,28 @@
[tox]
envlist = py36,py37,py38,py39
skip_missing_interpreters = True
skipsdist = True
minversion = 3.14.1
[testenv]
usedevelop = True
deps = -rtest_requirements.txt
passenv = COVERAGE_FILE
commands =
pytest {posargs}
py38,py39: black --check .
isort --check-only --settings-path setup.cfg .
flake8 novel_stats tests
[testenv:black]
commands =
black {posargs} .
[testenv:test]
commands =
pytest {posargs}
[testenv:isort]
deps = {[testenv]deps}
commands =
isort --settings-path setup.cfg .