From ed4dd508644f577a324af3f7a0adbb8c81bfcc96 Mon Sep 17 00:00:00 2001 From: Eric Dunham Date: Mon, 1 Mar 2021 10:41:47 -0600 Subject: [PATCH 1/2] Add setting to allow for empty alt text in images --- RELEASE.md | 3 +++ docs/settings.rst | 4 ++++ pelican/readers.py | 2 +- pelican/settings.py | 1 + pelican/tests/content/article_with_images.html | 9 +++++++++ pelican/tests/test_cache.py | 2 +- pelican/tests/test_readers.py | 12 ++++++++++++ 7 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 RELEASE.md create mode 100644 pelican/tests/content/article_with_images.html diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..f52fafd2a --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: minor + +Add setting to allow for empty `alt` attributes in images; it defaults to `False`, which is the current behavior. Empty `alt` text can be indicative of an accessibility oversight, but can be intentional and desired, e.g. https://webaim.org/techniques/alttext/, https://www.w3.org/WAI/tutorials/images/decorative/. diff --git a/docs/settings.rst b/docs/settings.rst index c66c42a31..1d5e1b7f8 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -364,6 +364,10 @@ Basic settings A list of metadata fields containing reST/Markdown content to be parsed and translated to HTML. +.. data:: IMAGES_ALLOW_EMPTY_ALT_TEXT = False + + If ``True``, warnings will not be emitted when empty ``alt`` attributes for images are found. + .. data:: PORT = 8000 The TCP port to serve content from the output folder via HTTP when pelican diff --git a/pelican/readers.py b/pelican/readers.py index 15d09908f..b8cf44e2c 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -574,7 +574,7 @@ def read_file(self, base_path, path, content_class=Page, fmt=None, self.cache_data(path, (content, reader_metadata)) metadata.update(_filter_discardable_metadata(reader_metadata)) - if content: + if not self.settings['IMAGES_ALLOW_EMPTY_ALT_TEXT'] and content: # find images with empty alt find_empty_alt(content, path) diff --git a/pelican/settings.py b/pelican/settings.py index ea3ee8eb7..9dd3da35b 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -164,6 +164,7 @@ def load_source(name, path): 'FORMATTED_FIELDS': ['summary'], 'PORT': 8000, 'BIND': '127.0.0.1', + 'IMAGES_ALLOW_EMPTY_ALT_TEXT': False } PYGMENTS_RST_OPTIONS = None diff --git a/pelican/tests/content/article_with_images.html b/pelican/tests/content/article_with_images.html new file mode 100644 index 000000000..4e2af9e7c --- /dev/null +++ b/pelican/tests/content/article_with_images.html @@ -0,0 +1,9 @@ + + + + + Images + + + + diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 564f1d315..76e228c2d 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -153,7 +153,7 @@ def test_article_object_caching(self): - empty.md - empty_with_bom.md """ - self.assertEqual(generator.readers.read_file.call_count, 6) + self.assertEqual(generator.readers.read_file.call_count, 7) def test_article_reader_content_caching(self): """Test raw article content caching at the reader level""" diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index ea5f3bdda..51af582c8 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -134,6 +134,18 @@ def test_find_empty_alt(self): 'Other images have empty alt attributes'} ) + @patch('pelican.readers.logger') + def test_read_file_with_images_allow_empty_alt_text_false(self, log_mock): + test_file = 'article_with_images.html' + self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=False) + assert 2 == log_mock.warning.call_count + + @patch('pelican.readers.logger') + def test_read_file_with_images_allow_empty_alt_text_true(self, log_mock): + test_file = 'article_with_images.html' + self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=True) + log_mock.warning.assert_not_called() + class RstReaderTest(ReaderTest): From f5cec6f13e88067f3cd7fea33e94df9d0dee61fc Mon Sep 17 00:00:00 2001 From: Eric Dunham Date: Wed, 3 Mar 2021 12:32:08 -0600 Subject: [PATCH 2/2] Fix --fatal to honor LOG_FILTER --- RELEASE.md | 4 +- docs/settings.rst | 4 -- pelican/log.py | 32 +++++-------- pelican/readers.py | 2 +- pelican/settings.py | 3 +- .../tests/content/article_with_images.html | 9 ---- pelican/tests/test_cache.py | 2 +- pelican/tests/test_log.py | 46 +++++++++++++++++++ pelican/tests/test_readers.py | 12 ----- 9 files changed, 63 insertions(+), 51 deletions(-) delete mode 100644 pelican/tests/content/article_with_images.html diff --git a/RELEASE.md b/RELEASE.md index f52fafd2a..38a5a2b6f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,3 @@ -Release type: minor +Release type: patch -Add setting to allow for empty `alt` attributes in images; it defaults to `False`, which is the current behavior. Empty `alt` text can be indicative of an accessibility oversight, but can be intentional and desired, e.g. https://webaim.org/techniques/alttext/, https://www.w3.org/WAI/tutorials/images/decorative/. +Address an issue where `--fatal=warnings|errors` would not honor entries in the `LOG_FILTER` setting. \ No newline at end of file diff --git a/docs/settings.rst b/docs/settings.rst index 1d5e1b7f8..c66c42a31 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -364,10 +364,6 @@ Basic settings A list of metadata fields containing reST/Markdown content to be parsed and translated to HTML. -.. data:: IMAGES_ALLOW_EMPTY_ALT_TEXT = False - - If ``True``, warnings will not be emitted when empty ``alt`` attributes for images are found. - .. data:: PORT = 8000 The TCP port to serve content from the output folder via HTTP when pelican diff --git a/pelican/log.py b/pelican/log.py index 325ac3ea6..6210ea5a6 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -149,24 +149,9 @@ def enable_filter(self): self.addFilter(LimitLogger.limit_filter) -class FatalLogger(LimitLogger): - warnings_fatal = False - errors_fatal = False - - def warning(self, *args, **kwargs): - super().warning(*args, **kwargs) - if FatalLogger.warnings_fatal: - raise RuntimeError('Warning encountered') - - def error(self, *args, **kwargs): - super().error(*args, **kwargs) - if FatalLogger.errors_fatal: - raise RuntimeError('Error encountered') - - -logging.setLoggerClass(FatalLogger) +logging.setLoggerClass(LimitLogger) # force root logger to be of our preferred class -logging.getLogger().__class__ = FatalLogger +logging.getLogger().__class__ = LimitLogger def supports_color(): @@ -194,11 +179,13 @@ def get_formatter(): return TextFormatter() +class FatalHandler(logging.Handler): + def emit(self, record): + sys.exit(1) + + def init(level=None, fatal='', handler=logging.StreamHandler(), name=None, logs_dedup_min_level=None): - FatalLogger.warnings_fatal = fatal.startswith('warning') - FatalLogger.errors_fatal = bool(fatal) - logger = logging.getLogger(name) handler.setFormatter(get_formatter()) @@ -209,6 +196,11 @@ def init(level=None, fatal='', handler=logging.StreamHandler(), name=None, if logs_dedup_min_level: LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level + if fatal.startswith('warning'): + logger.addHandler(FatalHandler(level=logging.WARNING)) + if fatal: + logger.addHandler(FatalHandler(level=logging.ERROR)) + def log_warnings(): import warnings diff --git a/pelican/readers.py b/pelican/readers.py index b8cf44e2c..15d09908f 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -574,7 +574,7 @@ def read_file(self, base_path, path, content_class=Page, fmt=None, self.cache_data(path, (content, reader_metadata)) metadata.update(_filter_discardable_metadata(reader_metadata)) - if not self.settings['IMAGES_ALLOW_EMPTY_ALT_TEXT'] and content: + if content: # find images with empty alt find_empty_alt(content, path) diff --git a/pelican/settings.py b/pelican/settings.py index 9dd3da35b..393bee3ab 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -163,8 +163,7 @@ def load_source(name, path): 'WRITE_SELECTED': [], 'FORMATTED_FIELDS': ['summary'], 'PORT': 8000, - 'BIND': '127.0.0.1', - 'IMAGES_ALLOW_EMPTY_ALT_TEXT': False + 'BIND': '127.0.0.1' } PYGMENTS_RST_OPTIONS = None diff --git a/pelican/tests/content/article_with_images.html b/pelican/tests/content/article_with_images.html deleted file mode 100644 index 4e2af9e7c..000000000 --- a/pelican/tests/content/article_with_images.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - Images - - - - diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 76e228c2d..564f1d315 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -153,7 +153,7 @@ def test_article_object_caching(self): - empty.md - empty_with_bom.md """ - self.assertEqual(generator.readers.read_file.call_count, 7) + self.assertEqual(generator.readers.read_file.call_count, 6) def test_article_reader_content_caching(self): """Test raw article content caching at the reader level""" diff --git a/pelican/tests/test_log.py b/pelican/tests/test_log.py index 3f9d72503..53d83d29b 100644 --- a/pelican/tests/test_log.py +++ b/pelican/tests/test_log.py @@ -2,6 +2,7 @@ import unittest from collections import defaultdict from contextlib import contextmanager +from unittest import mock from pelican import log from pelican.tests.support import LogCountHandler @@ -130,3 +131,48 @@ def do_logging(): self.assertEqual( self.handler.count_logs('Another log \\d', logging.WARNING), 0) + + +@mock.patch.object(log, 'sys') +class TestLogInit(unittest.TestCase): + def init(self, fatal, name): + logger = logging.getLogger(name) + log.init(fatal=fatal, name=name) + return logger + + def test_fatal_warnings(self, sys): + logger = self.init('warnings', 'test_fatal_warnings') + logger.warning('foo') + logger.error('bar') + sys.exit.assert_called_with(1) + + def test_fatal_errors(self, sys): + logger = self.init('errors', 'test_fatal_errors') + logger.warning('foo') + logger.error('bar') + sys.exit.assert_called_with(1) + + def test_no_fatal(self, sys): + logger = self.init('', 'test_no_fatal') + logger.warning('foo') + logger.error('bar') + sys.exit.assert_not_called() + + def test_fatal_warnings_log_filter(self, sys): + limit_filter = log.LimitFilter() + limit_filter._ignore = {(logging.WARNING, 'foo')} + lf = mock.PropertyMock(return_value=limit_filter) + with mock.patch('pelican.log.LimitLogger.limit_filter', new=lf): + logger = self.init('warnings', 'test_fatal_warnings_log_filter') + logger.warning('foo') + sys.exit.assert_not_called() + + def test_fatal_errors_log_filter(self, sys): + limit_filter = log.LimitFilter() + limit_filter.LOGS_DEDUP_MIN_LEVEL = logging.CRITICAL + limit_filter._ignore = {(logging.ERROR, 'bar')} + lf = mock.PropertyMock(return_value=limit_filter) + with mock.patch('pelican.log.LimitLogger.limit_filter', new=lf): + logger = self.init('errors', 'test_fatal_errors_log_filter') + logger.error('bar') + sys.exit.assert_not_called() diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 51af582c8..ea5f3bdda 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -134,18 +134,6 @@ def test_find_empty_alt(self): 'Other images have empty alt attributes'} ) - @patch('pelican.readers.logger') - def test_read_file_with_images_allow_empty_alt_text_false(self, log_mock): - test_file = 'article_with_images.html' - self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=False) - assert 2 == log_mock.warning.call_count - - @patch('pelican.readers.logger') - def test_read_file_with_images_allow_empty_alt_text_true(self, log_mock): - test_file = 'article_with_images.html' - self.read_file(path=test_file, IMAGES_ALLOW_EMPTY_ALT_TEXT=True) - log_mock.warning.assert_not_called() - class RstReaderTest(ReaderTest):