anorack-0.3/0000755000000000000000000000000014773267274013013 5ustar00rootroot00000000000000anorack-0.3/.coveragerc0000644000000000000000000000022014773267057015125 0ustar00rootroot00000000000000[run] branch = true source = lib [report] show_missing = true exclude_lines = # (no coverage|Python >= [0-9.]+ is required)\b # vim:ft=dosini anorack-0.3/.pylintrc0000644000000000000000000000074614773267057014666 0ustar00rootroot00000000000000[MASTER] load-plugins = pylint.extensions.check_elif [MESSAGES CONTROL] disable = bad-continuation, consider-using-f-string, duplicate-code, invalid-name, locally-disabled, no-else-return, too-few-public-methods, too-many-arguments, useless-option-value, [REPORTS] reports = no score = no msg-template = {path}:{line}: {C}: {symbol} [{obj}] {msg} [FORMAT] max-line-length = 120 expected-line-ending-format = LF # vim:ft=dosini ts=4 sts=4 sw=4 et anorack-0.3/Makefile0000644000000000000000000000452414773267057014457 0ustar00rootroot00000000000000# Copyright © 2012-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. PYTHON = python3 PREFIX = /usr/local DESTDIR = bindir = $(PREFIX)/bin basedir = $(PREFIX)/share/anorack mandir = $(PREFIX)/share/man .PHONY: all all: ; .PHONY: install install: anorack $(PYTHON) - < lib/__init__.py # Python version check # executable: install -d $(DESTDIR)$(bindir) python_exe=$$($(PYTHON) -c 'import sys; print(sys.executable)') && \ sed \ -e "1 s@^#!.*@#!$$python_exe@" \ -e "s#^basedir = .*#basedir = '$(basedir)/'#" \ $(<) > $(<).tmp install $(<).tmp $(DESTDIR)$(bindir)/$(<) rm $(<).tmp # data: install -d $(DESTDIR)$(basedir)/data install -p -m644 data/* $(DESTDIR)$(basedir)/data/ # library: install -d $(DESTDIR)$(basedir)/lib install -p -m644 lib/*.py $(DESTDIR)$(basedir)/lib/ ifeq "$(DESTDIR)" "" umask 022 && $(PYTHON) -m compileall -q $(basedir)/lib/ endif ifeq "$(wildcard doc/*.1)" "" # run "$(MAKE) -C doc" to build the manpage else # manual page: install -d $(DESTDIR)$(mandir)/man1 install -p -m644 doc/$(<).1 $(DESTDIR)$(mandir)/man1/ endif .PHONY: test test: $(PYTHON) -bb -m unittest discover --verbose .PHONY: clean clean: find . -type f -name '*.py[co]' -delete find . -type d -name '__pycache__' -delete rm -f .coverage rm -f *.tmp .error = GNU make is required # vim:ts=4 sts=4 sw=4 noet anorack-0.3/anorack0000755000000000000000000000253314773267057014361 0ustar00rootroot00000000000000#!/usr/bin/env python3 # encoding=UTF-8 # Copyright © 2016-2024 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys basedir = None if basedir is not None: sys.path[:0] = [basedir] import lib.cli # pylint: disable=wrong-import-position if __name__ == '__main__': lib.cli.main() # vim:ts=4 sts=4 sw=4 et anorack-0.3/data/0000755000000000000000000000000014773267057013723 5ustar00rootroot00000000000000anorack-0.3/data/overrides0000644000000000000000000000022214773267057015644 0ustar00rootroot00000000000000EWMH E.W.M.H UCS U.C.S UDP U.D.P UPS U.P.S UTF U.T.F UTS U.T.S UUID U.U.I.D UUIDv U.U.I.D_v src source unary [[j'un@ri]] [[jˈunəɹi]] usr U.S.R anorack-0.3/doc/0000755000000000000000000000000014773267275013561 5ustar00rootroot00000000000000anorack-0.3/doc/LICENSE0000644000000000000000000000207414773267057014567 0ustar00rootroot00000000000000Copyright © 2012-2025 Jakub Wilk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. anorack-0.3/doc/Makefile0000644000000000000000000000364014773267057015222 0ustar00rootroot00000000000000# Copyright © 2014-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. export LC_ALL=C rst2man = $(notdir $(shell command -v rst2man || echo rst2man.py)) rst2xml = $(notdir $(shell command -v rst2xml || echo rst2xml.py)) exe = anorack .PHONY: all all: $(exe).1 $(exe).1: manpage.rst $(rst2man) --input-encoding=UTF-8 < $(<) > $(@).tmp perl -pi -e '/^[.]BI\b/ and s/\\fP/\\fR/g' $(@).tmp # work-around for https://bugs.debian.org/806601 perl -ni -e 'print unless /^[.]\\" vim:/' $(@).tmp mv $(@).tmp $(@) .PHONY: check check: check-changelog check-rst .PHONY: check-changelog check-changelog: changelog dpkg-parsechangelog -l$(<) --all 2>&1 >/dev/null | { ! grep .; } .PHONY: check-rst check-rst: ls README *.rst | xargs -t -I{} $(rst2xml) --input-encoding=UTF-8 --strict {} > /dev/null .PHONY: clean clean: rm -f $(exe).1 *.tmp .error = GNU make is required # vim:ts=4 sts=4 sw=4 noet anorack-0.3/doc/README0000644000000000000000000000234314773267057014441 0ustar00rootroot00000000000000Overview ======== The English language has two indefinite articles: + *a*: used before words that begin with a consonant sound (e.g., *a program*, *a host*, *a user*); + *an*: used before words that begin with a vowel sound (e.g., *an example*, *an hour*, *an undefined variable*). **anorack** is a specialized spell-checker that finds incorrect indefinite articles: .. code:: console $ cat test a Ubuntu user a 8-byte word an username $ anorack test test:1: a Ubuntu -> an Ubuntu /u:b'u:ntu:/ test:2: a 8 -> an 8 /'eIt/ test:3: an username -> a username /j'u:z3n,eIm/ Prerequisites ============= * Python ≥ 3.7 * `eSpeak NG`_ or eSpeak_ ≥ 1.47.08 (shared library only) .. _eSpeak NG: https://github.com/espeak-ng/espeak-ng .. _eSpeak: https://espeak.sourceforge.net/ Installation ============ You can use anorack without installing it, straight out of unpacked source tarball or a VCS checkout. It's also possible to install it system-wide with:: # make install By default, ``make install`` installs the package to ``/usr/local``. You can specify a different installation prefix by setting the ``PREFIX`` variable, e.g.:: $ make install PREFIX="$HOME/.local" .. vim:ft=rst ts=3 sts=3 sw=3 et anorack-0.3/doc/anorack.10000644000000000000000000000366714773267275015275 0ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH ANORACK 1 "2023-01-16" "anorack 0.3" "" .SH NAME anorack \- “a” vs “an” checker . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .SH SYNOPSIS .sp \fBanorack\fP [\fIoption\fP\&...] [\fIfile\fP\&...] .SH DESCRIPTION .sp The English language has two indefinite articles: .INDENT 0.0 .IP \(bu 2 \fIa\fP: used before words that begin with a consonant sound (e.g., \fIa program\fP, \fIa host\fP, \fIa user\fP); .IP \(bu 2 \fIan\fP: used before words that begin with a vowel sound (e.g., \fIan example\fP, \fIan hour\fP, \fIan undefined variable\fP). .UNINDENT .sp \fBanorack\fP is a specialized spell\-checker that finds incorrect indefinite articles. .SH OPTIONS .INDENT 0.0 .TP .B \-e Exit with non\-zero status if any incorrect articles were found. .TP .B \-\-ipa Print phonemes using IPA (International Phonetic Alphabet) instead of ASCII phoneme mnemonics. .TP .B \-h\fP,\fB \-\-help Show help message and exit. .TP .B \-\-version Show version information and exit. .UNINDENT .SH EXAMPLE .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ cat test a Ubuntu user a 8\-byte word an username $ anorack test test:1: a Ubuntu \-> an Ubuntu /u:b\(aqu:ntu:/ test:2: a 8 \-> an 8 /\(aqeIt/ test:3: an username \-> a username /j\(aqu:z3n,eIm/ .ft P .fi .UNINDENT .UNINDENT .SH SEE ALSO .sp \fBespeak\fP(1) . .\" Generated by docutils manpage writer. . anorack-0.3/doc/changelog0000644000000000000000000001013214773267057015426 0ustar00rootroot00000000000000anorack (0.3) unstable; urgency=low * Add the -e option (to exit with non-zero status if any incorrect articles were found). https://github.com/jwilk/anorack/issues/9 * Add override for “UUIDv” (as in “UUIDv4”). * Clarify help message for --ipa. * Improve README: + Use HTTPS for espeak.sourceforge.net. + Clarify that only eSpeak (NG) shared library is needed. * Fix minor bugs in IO: + Fix stderr buffering. + Don't reset encoding error handler to “strict”. * Exit with status 1 (not 2) on argument parsing error. * Drop support for Python < 3.7. -- Jakub Wilk Wed, 02 Apr 2025 19:04:39 +0200 anorack (0.2.8) unstable; urgency=low * Drop support for Python < 3.6. * Stop using the distutils module. (It's deprecated and scheduled for removal in Python 3.12.) * Improve the test suite: + Stop using nose for the test suite. + Disallow dubious operations on bytes. -- Jakub Wilk Wed, 24 Aug 2022 20:49:50 +0200 anorack (0.2.7) unstable; urgency=low * Add override for “UPS”. https://github.com/jwilk/anorack/issues/6 Thanks to Martin Michlmayr for the bug report. * Fix parsing words with intra-word apostrophes. https://github.com/jwilk/anorack/issues/7 Thanks to Martin Michlmayr for the bug report. -- Jakub Wilk Mon, 01 Jun 2020 20:12:29 +0200 anorack (0.2.6) unstable; urgency=low * Add override for “src”. https://github.com/jwilk/anorack/issues/5 Thanks to Paul Wise and Emmanuel Arias for the bug report. -- Jakub Wilk Mon, 25 May 2020 14:49:32 +0200 anorack (0.2.5) unstable; urgency=low * Drop support for Python 3.2. * Don't die with exception when a file cannot be opened. (If there are many input files, it's helpful to continue when one of them cannot be opened.) * Improve the build system: + Check Python version on install. + Byte-compile Python code on install (if DESTDIR is not set). + Add checks against BSD make. (Only GNU make is supported.) + Don't require GNU install(1). + Remove the test coverage file in the clean target. * Rephrase description of --version in help messages. * Improve the test suite. -- Jakub Wilk Mon, 21 Oct 2019 18:08:22 +0200 anorack (0.2.4) unstable; urgency=low * Reset the SIGPIPE signal disposition. * Improve the build system. -- Jakub Wilk Fri, 18 May 2018 21:05:06 +0200 anorack (0.2.3) unstable; urgency=low * Rewrite shebang at install time. * Make the doc makefile more portable. * Add installation instructions to README. * Improve the test suite. -- Jakub Wilk Wed, 22 Mar 2017 18:46:51 +0100 anorack (0.2.2) unstable; urgency=low * Fix compatibility with eSpeak >= 1.48.11. * Add support for eSpeak NG. * Put license into a separate file. -- Jakub Wilk Wed, 19 Oct 2016 20:11:33 +0200 anorack (0.2.1) unstable; urgency=low * Explain the grammar rules in README and in the manual page. * Don't disable stdout/stderr line buffering. -- Jakub Wilk Sun, 21 Aug 2016 21:31:00 +0200 anorack (0.2) unstable; urgency=low * Fix word-splitting for compounds that include numbers or underscores. This fixes, among others, false positives involving acronyms such as “a UTF16”. * Retain original article's case in correction. * Add option for printing phonemes using IPA (--ipa). * Add Makefile. * Use /usr/bin/env in shebang. -- Jakub Wilk Mon, 18 Jul 2016 12:12:40 +0200 anorack (0.1.1) unstable; urgency=low * Add the manual page. * Add N (ŋ) to the consonants set. * Allow quotation character between the article and the other word. * Fix false positives for the following phrases: + an EWMH + a UCS + a UDP + a UTF + a UTS + a UUID + a unary + a usr * Improve the test suite. -- Jakub Wilk Mon, 11 Jul 2016 21:42:44 +0200 anorack (0.1) unstable; urgency=low * Initial release. -- Jakub Wilk Mon, 04 Jul 2016 21:04:36 +0200 anorack-0.3/doc/manpage.rst0000644000000000000000000000221314773267057015717 0ustar00rootroot00000000000000======= anorack ======= ------------------- “a” vs “an” checker ------------------- :manual section: 1 :version: anorack 0.3 :date: 2023-01-16 Synopsis -------- **anorack** [*option*...] [*file*...] Description ----------- The English language has two indefinite articles: + *a*: used before words that begin with a consonant sound (e.g., *a program*, *a host*, *a user*); + *an*: used before words that begin with a vowel sound (e.g., *an example*, *an hour*, *an undefined variable*). **anorack** is a specialized spell-checker that finds incorrect indefinite articles. Options ------- -e Exit with non-zero status if any incorrect articles were found. --ipa Print phonemes using IPA (International Phonetic Alphabet) instead of ASCII phoneme mnemonics. -h, --help Show help message and exit. --version Show version information and exit. Example ------- :: $ cat test a Ubuntu user a 8-byte word an username $ anorack test test:1: a Ubuntu -> an Ubuntu /u:b'u:ntu:/ test:2: a 8 -> an 8 /'eIt/ test:3: an username -> a username /j'u:z3n,eIm/ See also -------- **espeak**\ (1) .. vim:ts=3 sts=3 sw=3 anorack-0.3/lib/0000755000000000000000000000000014773267057013560 5ustar00rootroot00000000000000anorack-0.3/lib/__init__.py0000644000000000000000000000025214773267057015670 0ustar00rootroot00000000000000''' anorack's private modules ''' # pylint: disable=multiple-statements async def _(): return f'{await "# Python >= 3.7 is required #"}' del _ # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/articles.py0000644000000000000000000000315214773267057015741 0ustar00rootroot00000000000000# Copyright © 2016-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' English articles ''' from lib import phonetics accents = str.join('', phonetics.accents) def choose_art(phonemes): ''' choose correct article for the phonemes: return "a" or "an" or NotImplemented ''' try: p = phonemes.strip(accents)[0] except IndexError: return NotImplemented if p in phonetics.consonants: return 'a' elif p in phonetics.vowels: return 'an' else: return NotImplemented __all__ = ['choose_art'] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/cli.py0000644000000000000000000001011514773267057014677 0ustar00rootroot00000000000000# Copyright © 2016-2023 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' anorack CLI ''' import argparse import signal import sys from lib.articles import choose_art from lib.io import ( open_file, ) from lib.misc import coerce_case, warn from lib.parser import parse_file from lib.phonetics import init as init_phonetics, text_to_phonemes from lib.version import __version__ class ArgumentParser(argparse.ArgumentParser): ''' ArgumentParser with exit status 1 ''' def exit(self, status=0, message=None): if status: status = 1 argparse.ArgumentParser.exit(self, status=status, message=message) class VersionAction(argparse.Action): ''' argparse --version action ''' def __init__(self, option_strings, dest=argparse.SUPPRESS): super().__init__( option_strings=option_strings, dest=dest, nargs=0, help='show version information and exit' ) def __call__(self, parser, namespace, values, option_string=None): from lib import espeak # pylint: disable=import-outside-toplevel print(f'{parser.prog} {__version__}') print('+ Python {0}.{1}.{2}'.format(*sys.version_info)) ng = ' NG' if espeak.ng else '' print(f'+ eSpeak{ng} {espeak.version}') parser.exit() def check_word(loc, art, word, *, ipa=False): ''' check if the word has correct article ''' phon = text_to_phonemes(word, ipa=ipa) correct_art = choose_art(phon) if correct_art is NotImplemented: warn(f"can't determine correct article for {word!r} /{phon}/") elif art.lower() != correct_art: correct_art = coerce_case(art, correct_art) print(f'{loc}: {art} {word} -> {correct_art} {word} /{phon}/') return False return True def main(): ''' run the program ''' signal.signal(signal.SIGPIPE, signal.SIG_DFL) ap = ArgumentParser(description='"a" vs "an" checker') ap.add_argument('--version', action=VersionAction) ap.add_argument('-e', action='store_true', help='exit with non-zero status if issues were found') ap.add_argument('--ipa', action='store_true', help='use IPA instead of ASCII phoneme mnemonics') ap.add_argument('--traceback', action='store_true', help=argparse.SUPPRESS) ap.add_argument('files', metavar='FILE', nargs='*', default=['-'], help='file to check (default: stdin)') options = ap.parse_args() init_phonetics() ok = True rc = 0 for path in options.files: try: file = open_file(path, encoding=sys.stdout.encoding, errors='replace') except OSError as exc: if options.traceback: raise msg = f'{ap.prog}: {path}: {exc.strerror}' print(msg, file=sys.stderr) rc = 1 continue with file: for loc, art, word in parse_file(file): ok &= check_word(loc, art, word, ipa=options.ipa) if rc == 0 and options.e and not ok: rc = 2 sys.exit(rc) __all__ = ['main'] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/espeak.py0000644000000000000000000001143614773267057015407 0ustar00rootroot00000000000000# Copyright © 2016-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' interface to eSpeak (NG) ''' import ctypes import itertools try: _shlib = ctypes.CDLL('libespeak-ng.so.1') ng = True except OSError: # no coverage _shlib = ctypes.CDLL('libespeak.so.1') ng = False # const char *espeak_Info(const char **path_data) _info = _shlib.espeak_Info _info.argtypes = [ctypes.POINTER(ctypes.c_char_p)] _info.restype = ctypes.c_char_p def info(): ''' return eSpeak version information ''' dummy = ctypes.c_char_p(b'') res = _info(ctypes.byref(dummy)) return res.decode('ASCII') version = info().split()[0] del info def vcmp(v1, v2): ''' cmp()-style version comparison ''' v1 = v1.split('.') v2 = v2.split('.') for c1, c2 in itertools.zip_longest(v1, v2, fillvalue=0): c1 = int(c1) c2 = int(c2) if c1 > c2: return 1 elif c1 < c2: return -1 return 0 # int espeak_Initialize(espeak_AUDIO_OUTPUT output, int buflength, const char *path, int options) _initialize = _shlib.espeak_Initialize _initialize.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_int] _initialize.restype = ctypes.c_int def init(): ''' initialize eSpeak ''' rc = _initialize(0, 0, None, 0) if rc <= 0: raise RuntimeError('espeak_Initialize(): internal error') # no coverage # espeak_ERROR espeak_SetVoiceByName(const char *name) _set_voice_by_name = _shlib.espeak_SetVoiceByName _set_voice_by_name.argtypes = [ctypes.c_char_p] _set_voice_by_name.restype = ctypes.c_int def set_voice_by_name(s): ''' use this voice for synthesis ''' s = s.encode('ASCII') rc = _set_voice_by_name(s) if rc == 0: return else: # no coverage if rc == -1: msg = 'internal error' elif rc == 1: msg = 'the command could not be buffered' else: msg = f'unknown error {rc}' raise RuntimeError('espeak_SetVoiceByName(): ' + msg) if vcmp(version, '1.48.1') >= 0: # const char *espeak_TextToPhonemes(const void **textptr, int textmode, int phonememode) _text_to_phonemes = _shlib.espeak_TextToPhonemes _text_to_phonemes.restype = ctypes.c_char_p _text_to_phonemes.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.c_int, ctypes.c_int] def text_to_phonemes(s, *, ipa=False): ''' translate text to phonemes ''' s = s.encode('UTF-8') z = ctypes.c_char_p(s) zptr = ctypes.pointer(z) assert zptr.contents is not None if vcmp(version, '1.48.11') >= 0: ipa = ipa << 1 else: ipa = ipa << 4 # no coverage res = _text_to_phonemes(zptr, 1, ipa) if zptr.contents.value is not None: raise RuntimeError # no coverage return res.decode('UTF-8').strip() elif vcmp(version, '1.47.08') >= 0: # no coverage # void espeak_TextToPhonemes(const void *text, char *buffer, int size, int textmode, int phonememode) _text_to_phonemes = _shlib.espeak_TextToPhonemes _text_to_phonemes.restype = ctypes.c_char_p _text_to_phonemes.argtypes = [ ctypes.c_char_p, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.c_int, ctypes.c_int ] def text_to_phonemes(s, *, ipa=False): ''' translate text to phonemes ''' s = s.encode('UTF-8') bufsize = 250 buf = ctypes.create_string_buffer(bufsize) _text_to_phonemes(s, buf, bufsize, 1, ipa << 4) return buf.value.decode('UTF-8').strip() else: # no coverage raise RuntimeError('eSpeak >= 1.47.08 is required') __all__ = [ 'init', 'ng', 'set_voice_by_name', 'text_to_phonemes', 'version', ] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/io.py0000644000000000000000000000314314773267057014542 0ustar00rootroot00000000000000# Copyright © 2016-2025 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' I/O and encodings ''' import io import sys def open_file(path, *, encoding, errors): ''' open() with special case for "-" ''' if path == '-': return io.TextIOWrapper( sys.stdin.buffer, encoding=encoding, errors=errors, ) else: return open( # pylint: disable=consider-using-with path, 'rt', encoding=encoding, errors=errors, ) __all__ = [ 'open_file', ] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/misc.py0000644000000000000000000000340314773267057015065 0ustar00rootroot00000000000000# Copyright © 2016-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' miscellanea ''' import os import sys def warn(msg): ''' print warning message ''' prog = os.path.basename(sys.argv[0]) print(f'{prog}: warning: {msg}', file=sys.stderr) def _coerce_case(src, word): ''' coerce word to the same case as src (simple version doesn't support title-case) ''' if src.isupper(): return word.upper() else: return word.lower() def coerce_case(src, word): ''' coerce word to the same case as src ''' return ( _coerce_case(src[:1], word[:1]) + _coerce_case(src[1:], word[1:]) ) __all__ = [ 'warn', 'coerce_case', ] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/parser.py0000644000000000000000000000410214773267057015423 0ustar00rootroot00000000000000# Copyright © 2016-2024 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' English parser ''' import re class Location: ''' location in a file ''' def __init__(self, file, lineno): self.file = file self.lineno = lineno def __str__(self): return f'{self.file.name}:{self.lineno}' find_articles = re.compile( r'''(?,
, ) tuples ''' carry = '' for i, line in enumerate(file, start=1): cline = carry + line carry = '' for match in find_articles(cline): art, word, eol_art = match.groups() if art is not None: assert word is not None yield (Location(file, i), art, word) else: assert eol_art is not None carry = eol_art + ' ' __all__ = ['parse_file'] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/phonetics.py0000644000000000000000000000500514773267057016126 0ustar00rootroot00000000000000# Copyright © 2016-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ''' English phonetics ''' import functools import os consonants = frozenset( 'DNSTZbdfghjklmnprstvwz' 'ðŋʃθʒbdfɡhjkɬmnpɹstvwz' ) vowels = frozenset( '03@AEIOUVaeiou' 'ɒɜəɑɛɪɔʊʌɐeiɔu' ) accents = frozenset( ",'" "ˌˈ" ) espeak = None overrides = {} def init(): ''' initialize underlying speech engine ''' global espeak # pylint: disable=global-statement,global-variable-not-assigned from lib import espeak # pylint: disable=redefined-outer-name,import-outside-toplevel espeak.init() espeak.set_voice_by_name('en') here = os.path.dirname(__file__) # Ideally false positives should be fixed in eSpeak, # but as a stop-gap measure, we carry data file to correct some of them. path = f'{here}/../data/overrides' with open(path, 'rt', encoding='UTF-8') as file: for line in file: line = line.strip() (word, phon) = line.split('\t', 1) word = word.lower() overrides[word] = phon @functools.lru_cache(maxsize=9999) def text_to_phonemes(s, *, ipa=False): ''' translate text to phonemes ''' s = overrides.get(s.lower(), s) if s.startswith('[[') and s.endswith(']]'): return s.split('\t')[ipa][2:-2] else: return espeak.text_to_phonemes(s, ipa=ipa) __all__ = [ 'consonants', 'vowels', 'accents', 'text_to_phonemes', ] # vim:ts=4 sts=4 sw=4 et anorack-0.3/lib/version.py0000644000000000000000000000005514773267057015617 0ustar00rootroot00000000000000'''anorack's version''' __version__ = '0.3' anorack-0.3/private/0000755000000000000000000000000014773267274014465 5ustar00rootroot00000000000000anorack-0.3/private/run-pylint0000755000000000000000000000320414773267057016532 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2015-2018 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. set -e -u PYTHON=${PYTHON:-python3} "$PYTHON" -m pylint --version >/dev/null || exit 1 if [ $# -eq 0 ] then pyscripts=$(grep -l -r '^#!.*python' .) set -- lib tests $pyscripts fi if [ -n "${VIRTUAL_ENV:-}" ] then # https://github.com/PyCQA/pylint/issues/73 set -- --ignored-modules=distutils "$@" fi log=$(mktemp -t pylint.XXXXXX) "$PYTHON" -m pylint "$@" > "$log" || [ $? != 1 ] ! grep -P '^\S+:' "$log" \ | grep -v -P '^(?!lib/).*: missing-(\w+-)?docstring ' \ | grep '.' || exit 1 rm "$log" # vim:ts=4 sts=4 sw=4 et anorack-0.3/private/update-coverage0000755000000000000000000000263614773267057017474 0ustar00rootroot00000000000000#!/bin/sh # Copyright © 2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. set -e -u cd "${0%/*}/.." true ${PYTHON:=python3} "$PYTHON" -m coverage run -m unittest discover cat > tests/coverage.tmp <> tests/coverage.tmp mv tests/coverage.tmp tests/coverage # vim:ts=4 sts=4 sw=4 et anorack-0.3/private/update-version0000755000000000000000000000054714773267057017365 0ustar00rootroot00000000000000#!/bin/sh set -e -u version=${1:?"no version number provided"} date="$(date -u --rfc-3339=date)" PS4='$ ' set -x dch -m -v "$version" -u low -c doc/changelog export version date perl -pi -e 's/^__version__ = '"'"'\K[\w.]+/$ENV{version}/' lib/version.py perl -pi -e 's/^:version: \S+ \K[\w.]+/$ENV{version}/; s/^(:date:) \K[0-9-]+/$ENV{date}/' doc/manpage.rst anorack-0.3/tests/0000755000000000000000000000000014773267057014154 5ustar00rootroot00000000000000anorack-0.3/tests/__init__.py0000644000000000000000000000015514773267057016266 0ustar00rootroot00000000000000# pylint: disable=multiple-statements async def _(): return f'{await "# Python >= 3.7 is required #"}' del _ anorack-0.3/tests/coverage0000644000000000000000000000142114773267057015670 0ustar00rootroot00000000000000Generated automatically by private/update-coverage. Do not edit. Name Stmts Miss Branch BrPart Cover Missing -------------------------------------------------------------- lib/__init__.py 1 0 0 0 100% lib/articles.py 14 0 4 0 100% lib/cli.py 64 0 16 0 100% lib/espeak.py 55 0 6 0 100% lib/io.py 8 0 2 0 100% lib/misc.py 13 0 2 0 100% lib/parser.py 22 0 6 0 100% lib/phonetics.py 27 0 8 0 100% lib/version.py 1 0 0 0 100% -------------------------------------------------------------- TOTAL 205 0 44 0 100% anorack-0.3/tests/test_choose_art.py0000644000000000000000000000341114773267057017712 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from tests.tools import ( assert_equal, testcase, ) import lib.articles as M @testcase def test_choose_a(): art = M.choose_art("sp'am") assert_equal(art, 'a') @testcase def test_choose_a_ipa(): art = M.choose_art("θˈɜːmɪdˌɔː") assert_equal(art, 'a') @testcase def test_choose_an(): art = M.choose_art("'Eg") assert_equal(art, 'an') @testcase def test_choose_an_ipa(): art = M.choose_art("ˈɛɡ") assert_equal(art, 'an') @testcase def test_choose_other(): art = M.choose_art('%') assert_equal(art, NotImplemented) art = M.choose_art('') assert_equal(art, NotImplemented) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_cli.py0000644000000000000000000001531114773267057016335 0ustar00rootroot00000000000000# Copyright © 2016-2024 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import contextlib import errno import io import os import sys import tempfile import unittest.mock from tests.tools import ( assert_equal, assert_is_instance, assert_not_equal, isolation, testcase, ) @contextlib.contextmanager def tmpcwd(): with tempfile.TemporaryDirectory(prefix='anorack.tests.') as tmpdir: orig_cwd = os.getcwd() os.chdir(tmpdir) try: yield finally: os.chdir(orig_cwd) def TextIO(s=None, *, name): fp = io.BytesIO(s) fp.name = name return io.TextIOWrapper(fp, encoding='UTF-8') class CompletedProcess: def __init__(self, rc, stdout, stderr): self.rc = rc self.stdout = stdout self.stderr = stderr def __run_main(argv, stdin): sys.argv = argv if stdin is not None: if isinstance(stdin, str): stdin = stdin.encode('UTF-8') sys.stdin = mock_stdin = TextIO(stdin, name=sys.__stdin__.name) else: mock_stdin = None sys.stdout = mock_stdout = TextIO(name=sys.__stdout__.name) sys.stderr = mock_stderr = TextIO(name=sys.__stderr__.name) import lib.cli # pylint: disable=bad-option-value,import-outside-toplevel rc = 0 try: lib.cli.main() except SystemExit as exc: rc = exc.code except OSError as exc: rc = exc yield rc for fp in (sys.stdout, sys.stderr): fp.flush() s = fp.buffer.getvalue() # pylint: disable=no-member yield s.decode('UTF-8') del mock_stdin, mock_stdout, mock_stderr def _run_main(argv, stdin): # abuse mock to save&restore sys.argv, sys.stdin, etc.: with unittest.mock.patch.multiple(sys, argv=None, stdin=None, stdout=None, stderr=None): return CompletedProcess(*__run_main(argv, stdin)) run_main = isolation(_run_main) def t(*, stdin=None, files=None, stdout, stdout_ipa=None, stderr='', stderr_ipa=None): if stdout_ipa is None: stdout_ipa = stdout if stderr_ipa is None: stderr_ipa = stderr argv = ['anorack'] if files is not None: for (name, content) in files: with open(name, 'wt', encoding='UTF-8') as file: file.write(content) argv += [name] actual = run_main(argv, stdin) if '-@' in stdout: stdout = stdout.replace('-@', '@') actual.stdout = actual.stdout.replace('-@', '@') assert_equal(stdout, actual.stdout) assert_equal(stderr, actual.stderr) assert_equal(actual.rc, 0) argv += ['--ipa'] actual = run_main(argv, stdin) actual.stderr = actual.stderr.replace('t͡ʃ', 'tʃ') assert_equal(stdout_ipa, actual.stdout) assert_equal(stderr_ipa, actual.stderr) assert_equal(actual.rc, 0) @testcase def test_stdin(): t( stdin=( 'It could be carried by an African swallow!\n' 'Oh, yeah, a African swallow maybe, but not an\n' 'European swallow.\n' ), stdout=( ":2: a African -> an African /'afrIk@n/\n" ":3: an European -> a European /j,U@r-@p'i@n/\n" ), stdout_ipa=( ":2: a African -> an African /ˈafɹɪkən/\n" ":3: an European -> a European /jˌʊəɹəpˈiən/\n" ), ) @testcase @tmpcwd() def test_files(): t( files=( ('holy', 'It could be carried by a African swallow!'), ('grail', 'Oh, yeah, an African swallow maybe, but not an European swallow.'), ), stdout=( "holy:1: a African -> an African /'afrIk@n/\n" "grail:1: an European -> a European /j,U@r-@p'i@n/\n" ), stdout_ipa=( "holy:1: a African -> an African /ˈafɹɪkən/\n" "grail:1: an European -> a European /jˌʊəɹəpˈiən/\n" ), ) @testcase def test_warning(): def dummy_choose_art(phon): del phon return NotImplemented with unittest.mock.patch('lib.cli.choose_art', dummy_choose_art): t( stdin='A scratch?!', stdout='', stderr="anorack: warning: can't determine correct article for 'scratch' /skr'atS/\n", stderr_ipa="anorack: warning: can't determine correct article for 'scratch' /skɹˈatʃ/\n", ) @testcase def test_bad_io(): argv = ['anorack', '/nonexistent', '-'] actual = run_main(argv, 'a African') assert_equal(':', actual.stdout[:8]) err = os.strerror(errno.ENOENT) stderr = f'{argv[0]}: {argv[1]}: {err}\n' assert_equal(stderr, actual.stderr) assert_equal(actual.rc, 1) argv[1:1] = ['--traceback'] actual = run_main(argv, 'a African') assert_equal('', actual.stdout) assert_equal('', actual.stderr) assert_is_instance(actual.rc, OSError) assert_equal(actual.rc.errno, errno.ENOENT) @testcase def test_e(): argv = ['anorack', '-e'] actual = run_main(argv, 'a African') assert_equal(':', actual.stdout[:8]) assert_equal('', actual.stderr) assert_equal(actual.rc, 2) @testcase def test_changelog(): argv = ['anorack', 'doc/changelog'] actual = run_main(argv, None) assert_equal('', actual.stdout) assert_equal('', actual.stderr) assert_equal(actual.rc, 0) @testcase def test_version(): argv = ['anorack', '--version'] actual = run_main(argv, None) assert_not_equal('', actual.stdout) assert_equal('', actual.stderr) assert_equal(actual.rc, 0) @testcase def test_bad_arg(): argv = ['anorack', '---'] actual = run_main(argv, None) assert_equal('', actual.stdout) assert_not_equal('', actual.stderr) assert_equal(actual.rc, 1) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_coerce_case.py0000644000000000000000000000310514773267057020017 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from tests.tools import ( assert_equal, testcase, ) import lib.misc as M def t(src, word, exp): res = M.coerce_case(src, word) assert_equal(exp, res) @testcase def test_a(): t('a', 'a', 'a') t('A', 'a', 'A') t('an', 'a', 'a') t('An', 'a', 'A') t('AN', 'a', 'A') @testcase def test_an(): t('a', 'an', 'an') t('A', 'an', 'An') t('an', 'an', 'an') t('An', 'an', 'An') t('AN', 'an', 'AN') del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_open_file.py0000644000000000000000000000321214773267057017523 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys import unittest from tests.tools import ( assert_equal, testcase, ) import lib.io as M def t(path): encoding = 'ISO-8859-2' errors = 'xmlcharrefreplace' with M.open_file(path, encoding=encoding, errors=errors) as file: assert_equal(file.encoding, encoding) assert_equal(file.errors, errors) @testcase def test_open_real_file(): t(os.devnull) @testcase def test_open_stdin(): with unittest.mock.patch('sys.stdin', sys.__stdin__): t('-') del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_parse_file.py0000644000000000000000000000513114773267057017676 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import io from tests.tools import ( assert_equal, assert_is, assert_is_instance, testcase, ) import lib.parser as M def t(s, exp): if isinstance(exp, tuple): exp = [exp] file = io.StringIO(s) result = list(M.parse_file(file)) assert_equal(len(result), len(exp)) for (loc, art, word), (xi, xart, xword) in zip(result, exp): assert_is_instance(loc, M.Location) assert_is(loc.file, file) assert_equal(loc.lineno, xi) assert_equal(xart, art) assert_equal(xword, word) @testcase def test_mid_line(): t('Oh, yeah, an African swallow maybe,\nbut not a European swallow.\n', [ (1, 'an', 'African'), (2, 'a', 'European'), ]) @testcase def test_wrapped(): t('I thought we were an\nautonomous collective.', (2, 'an', 'autonomous')) @testcase def test_quotes(): t( "a 'scratch'\n" 'a ‘scratch’\n' 'a "scratch"\n' 'a “scratch”\n', [(i, 'a', 'scratch') for i in range(1, 5)] ) @testcase def test_underscore(): t('a European_swallow', (1, 'a', 'European')) @testcase def test_numbers(): t('an 8', (1, 'an', '8')) t('an 8bit', (1, 'an', '8')) t('an 8-bit', (1, 'an', '8')) t('an 8 bit', (1, 'an', '8')) t('a UTF16', (1, 'a', 'UTF')) t('a UTF-16', (1, 'a', 'UTF')) t('a UTF 16', (1, 'a', 'UTF')) @testcase def test_inner_apostrophe(): t("Esra'a Al Shafei", []) t('Esra’a Al Shafei', []) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_phonetics.py0000644000000000000000000000455414773267057017571 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from tests.tools import ( assert_equal, isolation, testcase, TestCase, ) import lib.phonetics as M def __test(word, xphon, xipa): M.init() phon = M.text_to_phonemes(word) assert_equal(xphon, phon) ipa = M.text_to_phonemes(word, ipa=True) ipa = ipa.replace('t͡ʃ', 'tʃ') assert_equal(xipa, ipa) _test = isolation(__test) class test_overrides(TestCase): pass def _init_test_overrides(): def add(word, xphon, xipa): @staticmethod def test(): _test(word, xphon, xipa) setattr(test_overrides, 'test_' + word, test) add('EWMH', ",i:d,Vb@Lj,u:,Em'eItS", 'ˌiːdˌʌbəljˌuːˌɛmˈeɪtʃ') add('UCS', "j,u:s,i:;'Es", 'jˌuːsˌiːˈɛs') add('UDP', "j,u:d,i:p'i:", 'jˌuːdˌiːpˈiː') add('UPS', "j,u:p,i:;'Es", 'jˌuːpˌiːˈɛs') add('UTF', "j,u:t,i:;'Ef", 'jˌuːtˌiːˈɛf') add('UTS', "j,u:t,i:;'Es", 'jˌuːtˌiːˈɛs') add('UUID', "j,u:j,u:,aId'i:", 'jˌuːjˌuːˌaɪdˈiː') add('UUIDv', "j,u:j,u:,aId'i: v'i:", 'jˌuːjˌuːˌaɪdˈiː vˈiː') add('src', "s'o@s", 'sˈɔːs') add('unary', "j'un@ri", "jˈunəɹi") add('usr', "j,u:,Es'A@", "jˌuːˌɛsˈɑː") _init_test_overrides() del _init_test_overrides del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_vcmp.py0000644000000000000000000000302614773267057016533 0ustar00rootroot00000000000000# Copyright © 2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from tests.tools import ( assert_equal, testcase, ) import lib.espeak as M def cmp(x, y): return (x > y) - (x < y) @testcase def test(): versions = ''' 1 1.04.71 1.47.08 1.48.1 1.48.11 1.48.15 3 3.14 '''.split() for i1, v1 in enumerate(versions): for i2, v2 in enumerate(versions): assert_equal(M.vcmp(v1, v2), cmp(i1, i2)) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_version.py0000644000000000000000000000365014773267057017256 0ustar00rootroot00000000000000# Copyright © 2012-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os from tests.tools import ( assert_equal, testcase, ) from lib.version import __version__ here = os.path.dirname(__file__) docdir = os.path.join(here, os.pardir, 'doc') @testcase def test_changelog(): path = os.path.join(docdir, 'changelog') with open(path, 'rt', encoding='UTF-8') as file: line = file.readline() changelog_version = line.split()[1].strip('()') assert_equal(changelog_version, __version__) @testcase def test_manpage(): path = os.path.join(docdir, 'manpage.rst') manpage_version = None with open(path, 'rt', encoding='UTF-8') as file: for line in file: if line.startswith(':version:'): manpage_version = line.split()[-1] break assert_equal(manpage_version, __version__) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/test_warn.py0000644000000000000000000000314014773267057016532 0ustar00rootroot00000000000000# Copyright © 2016-2021 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import io import unittest.mock from tests.tools import ( assert_equal, testcase, ) import lib.misc as M @testcase def test_warn(): stderr = io.StringIO() with unittest.mock.patch('sys.stderr', stderr): with unittest.mock.patch('sys.argv', ['/usr/bin/anorack']): M.warn('NOBODY expects the Spanish Inquisition!') assert_equal( stderr.getvalue(), 'anorack: warning: NOBODY expects the Spanish Inquisition!\n' ) del testcase # vim:ts=4 sts=4 sw=4 et anorack-0.3/tests/tools.py0000644000000000000000000000460514773267057015673 0ustar00rootroot00000000000000# Copyright © 2016-2022 Jakub Wilk # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the “Software”), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import concurrent.futures import functools import sys import unittest def isolation(f): if 'coverage' in sys.modules: # Process isolation would break coverage measurements. # Oh well. FIXME. return f else: @functools.wraps(f) def wrapper(*args, **kwargs): with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor: ftr = executor.submit(f, *args, **kwargs) return ftr.result() return wrapper def testcase(f): class TestCase(unittest.TestCase): # pylint: disable=redefined-outer-name @staticmethod def test(): return f() def __str__(self): return f'{f.__module__}.{f.__name__}' return TestCase tc = unittest.TestCase('__hash__') assert_equal = tc.assertEqual assert_is = tc.assertIs assert_is_instance = tc.assertIsInstance assert_not_equal = tc.assertNotEqual del tc class TestCase(unittest.TestCase): def __str__(self): cls = unittest.util.strclass(self.__class__) name = self._testMethodName return f'{cls}.{name}' __all__ = [ 'isolation', 'testcase', 'TestCase', # nose-compatible: 'assert_equal', 'assert_is', 'assert_is_instance', 'assert_not_equal', ] # vim:ts=4 sts=4 sw=4 et