reader 3.24__tar.gz → 3.26__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reader-3.24 → reader-3.26}/CHANGES.rst +36 -0
- {reader-3.24/src/reader.egg-info → reader-3.26}/PKG-INFO +4 -1
- {reader-3.24 → reader-3.26}/README.rst +1 -0
- {reader-3.24 → reader-3.26}/docs/api.rst +6 -0
- {reader-3.24 → reader-3.26}/docs/app.rst +16 -0
- {reader-3.24 → reader-3.26}/docs/conf.py +1 -1
- {reader-3.24 → reader-3.26}/docs/guide.rst +10 -7
- {reader-3.24 → reader-3.26}/docs/install.rst +8 -6
- {reader-3.24 → reader-3.26}/docs/internal.rst +29 -22
- {reader-3.24 → reader-3.26}/docs/plugins.rst +1 -0
- reader-3.26/docs/screenshots/autodiscovery.png +0 -0
- reader-3.26/docs/screenshots/export-database.png +0 -0
- {reader-3.24 → reader-3.26}/pyproject.toml +5 -1
- {reader-3.24 → reader-3.26}/src/reader/__init__.py +1 -1
- {reader-3.24 → reader-3.26}/src/reader/_app/__init__.py +71 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/cli.py +12 -1
- reader-3.26/src/reader/_app/exports.py +58 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/ext.py +29 -2
- {reader-3.24 → reader-3.26}/src/reader/_app/static/style.css +2 -4
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/add_feed.html +9 -1
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/entries.html +58 -17
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/entry.html +1 -2
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/help.html +8 -2
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/layout_base.html +18 -2
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/macros.html +13 -4
- reader-3.26/src/reader/_app/templates/settings.html +1 -0
- reader-3.26/src/reader/_app/templates/settings_base.html +67 -0
- {reader-3.24 → reader-3.26}/src/reader/_cli.py +12 -0
- reader-3.24/src/reader/_parser/_lazy.py → reader-3.26/src/reader/_parser/__init__.py +481 -26
- {reader-3.24 → reader-3.26}/src/reader/_parser/feedparser.py +8 -2
- reader-3.26/src/reader/_parser/http.py +299 -0
- {reader-3.24 → reader-3.26}/src/reader/_parser/jsonfeed.py +6 -2
- {reader-3.24 → reader-3.26}/src/reader/_plugins/legacy/preview_feed_list.py +13 -8
- {reader-3.24 → reader-3.26}/src/reader/_plugins/sqlite_releases.py +4 -2
- {reader-3.24 → reader-3.26}/src/reader/_storage/_base.py +31 -1
- {reader-3.24 → reader-3.26}/src/reader/_storage/_entries.py +1 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_html_utils.py +9 -4
- {reader-3.24 → reader-3.26}/src/reader/_storage/_schema.py +1 -1
- {reader-3.24 → reader-3.26}/src/reader/_types.py +14 -1
- {reader-3.24 → reader-3.26}/src/reader/_update/base.py +14 -13
- {reader-3.24 → reader-3.26}/src/reader/_update/hooks.py +34 -20
- {reader-3.24 → reader-3.26}/src/reader/_utils.py +0 -15
- {reader-3.24 → reader-3.26}/src/reader/core.py +20 -16
- reader-3.26/src/reader/discover.py +143 -0
- reader-3.26/src/reader/plugins/autodiscover.py +91 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/ua_fallback.py +4 -2
- {reader-3.24 → reader-3.26/src/reader.egg-info}/PKG-INFO +4 -1
- {reader-3.24 → reader-3.26}/src/reader.egg-info/SOURCES.txt +9 -4
- {reader-3.24 → reader-3.26}/src/reader.egg-info/requires.txt +2 -0
- {reader-3.24 → reader-3.26}/tests/fakeparser.py +1 -2
- {reader-3.24 → reader-3.26}/tests/test_cli.py +4 -1
- reader-3.26/tests/test_discover.py +48 -0
- {reader-3.24 → reader-3.26}/tests/test_html_utils.py +1 -1
- {reader-3.24 → reader-3.26}/tests/test_lazy_imports.py +1 -1
- {reader-3.24 → reader-3.26}/tests/test_parser.py +34 -17
- reader-3.26/tests/test_plugins_autodiscover.py +69 -0
- {reader-3.24 → reader-3.26}/tests/test_reader.py +2 -2
- {reader-3.24 → reader-3.26}/tests/test_reader_hooks.py +18 -1
- {reader-3.24 → reader-3.26}/tests/test_storage.py +32 -7
- reader-3.24/scripts/backup.sh +0 -36
- reader-3.24/src/reader/_parser/__init__.py +0 -510
- reader-3.24/src/reader/_parser/http.py +0 -107
- reader-3.24/src/reader/_parser/requests/__init__.py +0 -176
- reader-3.24/src/reader/_parser/requests/_lazy.py +0 -162
- {reader-3.24 → reader-3.26}/LICENSE +0 -0
- {reader-3.24 → reader-3.26}/MANIFEST.in +0 -0
- {reader-3.24 → reader-3.26}/docs/changelog.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/cli.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/config.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/contributing.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/dev.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/index.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/empty.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/entries-filters-light.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/entries.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/entry-image-light.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/entry.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/feed.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/feeds.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/help.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/import-result.png +0 -0
- {reader-3.24 → reader-3.26}/docs/screenshots/import-select.png +0 -0
- {reader-3.24 → reader-3.26}/docs/tutorial.rst +0 -0
- {reader-3.24 → reader-3.26}/docs/why.rst +0 -0
- {reader-3.24 → reader-3.26}/examples/config.toml +0 -0
- {reader-3.24 → reader-3.26}/examples/custom_headers.py +0 -0
- {reader-3.24 → reader-3.26}/examples/feed_slugs.py +0 -0
- {reader-3.24 → reader-3.26}/examples/parser_only.py +0 -0
- {reader-3.24 → reader-3.26}/examples/podcast.py +0 -0
- {reader-3.24 → reader-3.26}/examples/terminal.py +0 -0
- {reader-3.24 → reader-3.26}/run.sh +0 -0
- {reader-3.24 → reader-3.26}/scripts/bench.py +0 -0
- {reader-3.24 → reader-3.26}/scripts/debug_storage_stats.py +0 -0
- {reader-3.24 → reader-3.26}/scripts/entry_dedupe_backtest.py +0 -0
- {reader-3.24 → reader-3.26}/scripts/jscontrols.html +0 -0
- {reader-3.24 → reader-3.26}/scripts/jscontrols.py +0 -0
- {reader-3.24 → reader-3.26}/scripts/lines.sh +0 -0
- {reader-3.24 → reader-3.26}/scripts/release.py +0 -0
- {reader-3.24 → reader-3.26}/scripts/vendor_app_stuff.py +0 -0
- {reader-3.24 → reader-3.26}/setup.cfg +0 -0
- {reader-3.24 → reader-3.26}/src/reader/__main__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/forms.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/api_thing.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/static/controls.js +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/static/style.css +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/add_entry.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/entries.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/entry.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/feeds.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/layout.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/macros.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/metadata.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/templates/tags.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/legacy/wsgi.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/theme.js +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/README +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/css/bootstrap.min.css +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/htmx-ext-response-targets@2.0.4/dist/response-targets.min.js +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/static/vendor/htmx.org@2.0.8/dist/htmx.min.js +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/change_feed_title.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/delete_feed.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/feeds.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/import_feeds.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_app/wsgi.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_config_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_hash_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_logging.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_parser/file.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/legacy/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/legacy/enclosure_tags.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/legacy/share.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/legacy/templates/preview_feed_list.html +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_plugins/timer.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_changes.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_feeds.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_search.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_sqlite_utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_storage/_tags.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_update/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/exceptions.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/opml.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/_loader.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/entry_dedupe.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/mark_as_read.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/plugins/readtime.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/py.typed +0 -0
- {reader-3.24 → reader-3.26}/src/reader/types.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader/utils.py +0 -0
- {reader-3.24 → reader-3.26}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.24 → reader-3.26}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.24 → reader-3.26}/tests/_plugins/__init__.py +0 -0
- {reader-3.24 → reader-3.26}/tests/_plugins/builtin.py +0 -0
- {reader-3.24 → reader-3.26}/tests/_plugins/error_during_import.py +0 -0
- {reader-3.24 → reader-3.26}/tests/_plugins/error_import_error.py +0 -0
- {reader-3.24 → reader-3.26}/tests/conftest.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/10.json +0 -0
- {reader-3.24 → reader-3.26}/tests/data/10.json.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/custom +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.atom +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.atom.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.json +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.json.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.rss +0 -0
- {reader-3.24 → reader-3.26}/tests/data/empty.rss.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.atom +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.atom.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.json +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.json.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.rss +0 -0
- {reader-3.24 → reader-3.26}/tests/data/full.rss.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/invalid.json +0 -0
- {reader-3.24 → reader-3.26}/tests/data/invalid.json.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/relative.atom +0 -0
- {reader-3.24 → reader-3.26}/tests/data/relative.atom.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/relative.rss +0 -0
- {reader-3.24 → reader-3.26}/tests/data/relative.rss.py +0 -0
- {reader-3.24 → reader-3.26}/tests/data/sqlite_releases.html +0 -0
- {reader-3.24 → reader-3.26}/tests/data/unknown.json +0 -0
- {reader-3.24 → reader-3.26}/tests/data/unknown.json.py +0 -0
- {reader-3.24 → reader-3.26}/tests/fixtures.py +0 -0
- {reader-3.24 → reader-3.26}/tests/reader_methods.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test__types.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test__utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_app_legacy.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_app_wsgi.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_bench.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_changes.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_config_utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_exceptions.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_hash_utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_logging.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_opml.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_loader.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_readtime.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_plugins_ua_fallback.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_counts.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_deprecations.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_filter.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_integration.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_lifecycle.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_private.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_search.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_sort.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_update.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_reader_utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_search.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_sql_utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_sqlite_utils.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_tags.py +0 -0
- {reader-3.24 → reader-3.26}/tests/test_types.py +0 -0
- {reader-3.24 → reader-3.26}/tests/utils.py +0 -0
- {reader-3.24 → reader-3.26}/tox.ini +0 -0
|
@@ -6,6 +6,42 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.26
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2026-06-24
|
|
13
|
+
|
|
14
|
+
* Allow exporting the entire database (e.g. for backup or migration).
|
|
15
|
+
|
|
16
|
+
* Through the :meth:`~reader._types.StorageType.export` storage method.
|
|
17
|
+
* In the web app, on the settings page; :ref:`screenshots <screenshots-export>`.
|
|
18
|
+
* In the CLI, as a new ``export`` command.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Version 3.25
|
|
22
|
+
------------
|
|
23
|
+
|
|
24
|
+
Released 2026-05-25
|
|
25
|
+
|
|
26
|
+
* Stable feed autodiscovery support. (:issue:`404`)
|
|
27
|
+
|
|
28
|
+
* Add :mod:`~reader.plugins.autodiscover` built-in plugin.
|
|
29
|
+
* Add :mod:`reader.discover` module providing low level discovery.
|
|
30
|
+
* In the web app, show :mod:`~reader.plugins.autodiscover` suggestions
|
|
31
|
+
when adding a feed; :ref:`screenshots <screenshots-autodiscovery>`.
|
|
32
|
+
|
|
33
|
+
* Fix bugs that may cause the 3.24 database migration to fail. (:issue:`405`)
|
|
34
|
+
|
|
35
|
+
* Don't fail for ``John Doe (...) (email)`` RSS authors.
|
|
36
|
+
* Support missing :attr:`EntrySource` ``author`` field during migration.
|
|
37
|
+
|
|
38
|
+
Note there should be no integrity issues,
|
|
39
|
+
as migrations happen inside a transaction.
|
|
40
|
+
|
|
41
|
+
* Simplify parser lazy initialization (make it explicit and opt-in by plugins).
|
|
42
|
+
* Allow before/after update hook functions to take *fewer* arguments than documented.
|
|
43
|
+
|
|
44
|
+
|
|
9
45
|
Version 3.24
|
|
10
46
|
------------
|
|
11
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.26
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -49,6 +49,8 @@ Provides-Extra: app
|
|
|
49
49
|
Requires-Dist: flask>=0.10; extra == "app"
|
|
50
50
|
Requires-Dist: humanize>=4; extra == "app"
|
|
51
51
|
Requires-Dist: PyYAML; extra == "app"
|
|
52
|
+
Requires-Dist: click; extra == "app"
|
|
53
|
+
Requires-Dist: platformdirs; extra == "app"
|
|
52
54
|
Requires-Dist: WTForms; extra == "app"
|
|
53
55
|
Requires-Dist: Flask-WTF; extra == "app"
|
|
54
56
|
Requires-Dist: jinja2-fragments; extra == "app"
|
|
@@ -114,6 +116,7 @@ you'd probably call it a fat model.
|
|
|
114
116
|
* full-text search articles
|
|
115
117
|
* get statistics on feed and user activity
|
|
116
118
|
* import / export feeds as OPML
|
|
119
|
+
* automatically discover feeds in web pages
|
|
117
120
|
* write plugins to extend its functionality
|
|
118
121
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
119
122
|
|
|
@@ -50,6 +50,7 @@ you'd probably call it a fat model.
|
|
|
50
50
|
* full-text search articles
|
|
51
51
|
* get statistics on feed and user activity
|
|
52
52
|
* import / export feeds as OPML
|
|
53
|
+
* automatically discover feeds in web pages
|
|
53
54
|
* write plugins to extend its functionality
|
|
54
55
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
55
56
|
|
|
@@ -19,6 +19,8 @@ Features, as of *reader* version |version|:
|
|
|
19
19
|
* article view
|
|
20
20
|
* custom feed titles
|
|
21
21
|
* dark mode
|
|
22
|
+
* feed autodiscovery
|
|
23
|
+
* full data export
|
|
22
24
|
|
|
23
25
|
More features already implemented in the library
|
|
24
26
|
and the :ref:`legacy web app <legacy web app>`
|
|
@@ -86,6 +88,20 @@ Screenshots
|
|
|
86
88
|
|
|
87
89
|
contextual help
|
|
88
90
|
|
|
91
|
+
.. _screenshots-autodiscovery:
|
|
92
|
+
|
|
93
|
+
.. figure:: screenshots/autodiscovery.png
|
|
94
|
+
:width: 240px
|
|
95
|
+
|
|
96
|
+
feed autodiscovery
|
|
97
|
+
|
|
98
|
+
.. _screenshots-export:
|
|
99
|
+
|
|
100
|
+
.. figure:: screenshots/export-database.png
|
|
101
|
+
:width: 240px
|
|
102
|
+
|
|
103
|
+
database exports
|
|
104
|
+
|
|
89
105
|
|
|
90
106
|
Serving the web app
|
|
91
107
|
-------------------
|
|
@@ -139,15 +139,18 @@ since each connection would be to a *different* database::
|
|
|
139
139
|
|
|
140
140
|
.. _backups:
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
Backups
|
|
143
|
+
~~~~~~~
|
|
144
144
|
|
|
145
|
-
Making
|
|
145
|
+
Making backups generally depends on the storage used.
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
the
|
|
149
|
-
or
|
|
150
|
-
|
|
147
|
+
If the storage implements it, you can use
|
|
148
|
+
the :meth:`~reader._types.StorageType.export` method
|
|
149
|
+
or the ``export`` :doc:`CLI <cli>` command.
|
|
150
|
+
|
|
151
|
+
For the SQLite storage, you can also use
|
|
152
|
+
the `.backup command <https://sqlite.org/cli.html>`_
|
|
153
|
+
or `VACUUM INTO <https://www.sqlite.org/lang_vacuum.html>`_.
|
|
151
154
|
|
|
152
155
|
|
|
153
156
|
|
|
@@ -13,13 +13,10 @@ Dependencies
|
|
|
13
13
|
|
|
14
14
|
These packages will be installed automatically when installing *reader*:
|
|
15
15
|
|
|
16
|
-
* `feedparser`_ parses
|
|
17
|
-
* `requests`_ retrieves feeds from the internet
|
|
18
|
-
it replaces feedparser's default use of :mod:`urllib`
|
|
19
|
-
to make it easier to write plugins.
|
|
16
|
+
* `feedparser`_ parses Atom and RSSfeeds.
|
|
17
|
+
* `requests`_ retrieves feeds from the internet.
|
|
20
18
|
* `werkzeug`_ provides HTTP utilities.
|
|
21
|
-
* `beautifulsoup4`_ is used to strip HTML tags
|
|
22
|
-
to the search index.
|
|
19
|
+
* `beautifulsoup4`_ is used to strip HTML tags and by various plugins.
|
|
23
20
|
* `structlog`_ is used for structured logging.
|
|
24
21
|
* `typing-extensions`_ is used for :mod:`typing` backports.
|
|
25
22
|
|
|
@@ -58,8 +55,13 @@ As of version |version|, *reader* has the following extras:
|
|
|
58
55
|
* Specific plugins may require additional dependencies;
|
|
59
56
|
refer to their documentation for details.
|
|
60
57
|
|
|
58
|
+
Beautiful Soup supports `multiple parsers`_,
|
|
59
|
+
and will use the best available one;
|
|
60
|
+
for speed, install `lxml`_.
|
|
61
61
|
|
|
62
62
|
.. _beautifulsoup4: https://www.crummy.com/software/BeautifulSoup/
|
|
63
|
+
.. _multiple parsers: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser
|
|
64
|
+
.. _lxml: https://lxml.de/
|
|
63
65
|
.. _feedparser: https://feedparser.readthedocs.io/en/latest/
|
|
64
66
|
.. _requests: https://requests.readthedocs.io/
|
|
65
67
|
.. _werkzeug: https://werkzeug.palletsprojects.com/
|
|
@@ -31,16 +31,6 @@ Parser
|
|
|
31
31
|
:special-members: __call__
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
.. module:: reader._parser.requests
|
|
35
|
-
|
|
36
|
-
.. autoclass:: SessionFactory(...)
|
|
37
|
-
:members:
|
|
38
|
-
:special-members: __call__
|
|
39
|
-
|
|
40
|
-
.. autoclass:: SessionWrapper(...)
|
|
41
|
-
:members:
|
|
42
|
-
|
|
43
|
-
|
|
44
34
|
Protocols
|
|
45
35
|
~~~~~~~~~
|
|
46
36
|
|
|
@@ -79,18 +69,6 @@ Protocols
|
|
|
79
69
|
:show-inheritance:
|
|
80
70
|
|
|
81
71
|
|
|
82
|
-
.. module:: reader._parser.requests
|
|
83
|
-
:no-index:
|
|
84
|
-
|
|
85
|
-
.. autoclass:: RequestHook
|
|
86
|
-
:members:
|
|
87
|
-
:special-members: __call__
|
|
88
|
-
|
|
89
|
-
.. autoclass:: ResponseHook
|
|
90
|
-
:members:
|
|
91
|
-
:special-members: __call__
|
|
92
|
-
|
|
93
|
-
|
|
94
72
|
Data objects
|
|
95
73
|
~~~~~~~~~~~~
|
|
96
74
|
|
|
@@ -113,6 +91,35 @@ Data objects
|
|
|
113
91
|
:members:
|
|
114
92
|
|
|
115
93
|
|
|
94
|
+
Built-in retrievers and parsers
|
|
95
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
96
|
+
|
|
97
|
+
.. module:: reader._parser.http
|
|
98
|
+
|
|
99
|
+
.. autoclass:: HTTPRetriever
|
|
100
|
+
:members: request_hooks, response_hooks
|
|
101
|
+
|
|
102
|
+
.. autoclass:: RequestHook
|
|
103
|
+
:members:
|
|
104
|
+
:special-members: __call__
|
|
105
|
+
|
|
106
|
+
.. autoclass:: ResponseHook
|
|
107
|
+
:members:
|
|
108
|
+
:special-members: __call__
|
|
109
|
+
|
|
110
|
+
.. module:: reader._parser.file
|
|
111
|
+
|
|
112
|
+
.. autoclass:: FileRetriever
|
|
113
|
+
|
|
114
|
+
.. module:: reader._parser.feedparser
|
|
115
|
+
|
|
116
|
+
.. autoclass:: FeedparserParser
|
|
117
|
+
|
|
118
|
+
.. module:: reader._parser.jsonfeed
|
|
119
|
+
|
|
120
|
+
.. autoclass:: JSONFeedParser
|
|
121
|
+
|
|
122
|
+
|
|
116
123
|
.. _storage:
|
|
117
124
|
|
|
118
125
|
Storage
|
|
@@ -16,6 +16,7 @@ This is a list of built-in plugins that are considered stable.
|
|
|
16
16
|
See the :ref:`Plugins <plugins>` section of the user guide
|
|
17
17
|
for details on how built-in plugins are loaded.
|
|
18
18
|
|
|
19
|
+
.. automodule:: reader.plugins.autodiscover
|
|
19
20
|
.. automodule:: reader.plugins.enclosure_dedupe
|
|
20
21
|
.. automodule:: reader.plugins.entry_dedupe
|
|
21
22
|
.. automodule:: reader.plugins.mark_as_read
|
|
Binary file
|
|
Binary file
|
|
@@ -65,8 +65,11 @@ cli = [
|
|
|
65
65
|
app = [
|
|
66
66
|
"flask>=0.10",
|
|
67
67
|
"humanize>=4",
|
|
68
|
-
# for
|
|
68
|
+
# for legacy (tags)
|
|
69
69
|
"PyYAML",
|
|
70
|
+
# for cli
|
|
71
|
+
"click",
|
|
72
|
+
"platformdirs",
|
|
70
73
|
# for v2
|
|
71
74
|
"WTForms",
|
|
72
75
|
"Flask-WTF",
|
|
@@ -186,6 +189,7 @@ module = [
|
|
|
186
189
|
"reader.plugins.ua_fallback",
|
|
187
190
|
"reader.plugins.readtime",
|
|
188
191
|
"reader.plugins.mark_as_read",
|
|
192
|
+
"reader.plugins.autodiscover",
|
|
189
193
|
"reader._vendor.feedparser.*",
|
|
190
194
|
]
|
|
191
195
|
ignore_errors = true
|
|
@@ -19,6 +19,7 @@ from flask import render_template
|
|
|
19
19
|
from flask import render_template_string
|
|
20
20
|
from flask import request
|
|
21
21
|
from flask import Response
|
|
22
|
+
from flask import send_from_directory
|
|
22
23
|
from flask import stream_with_context
|
|
23
24
|
from flask import url_for
|
|
24
25
|
from flask_wtf.csrf import CSRFError
|
|
@@ -35,6 +36,9 @@ from reader import InvalidFeedURLError
|
|
|
35
36
|
from reader import opml
|
|
36
37
|
from reader import UpdateError
|
|
37
38
|
|
|
39
|
+
from .exports import ExportNotFoundError
|
|
40
|
+
from .exports import TooManyExportsError
|
|
41
|
+
from .ext import get_exports
|
|
38
42
|
from .ext import get_reader
|
|
39
43
|
from .ext import ReaderExtension
|
|
40
44
|
from .forms import AddFeed
|
|
@@ -88,6 +92,15 @@ def entries():
|
|
|
88
92
|
if feed_url := form.feed.data:
|
|
89
93
|
feed = reader.get_feed(feed_url)
|
|
90
94
|
|
|
95
|
+
autodiscover = None
|
|
96
|
+
if feed and feed.last_exception and not feed.last_updated:
|
|
97
|
+
autodiscover_tag = reader.make_reader_reserved_name('autodiscover')
|
|
98
|
+
hostname = urlparse(feed.url).hostname
|
|
99
|
+
autodiscover = [
|
|
100
|
+
(link, hostname and urlparse(link['href']).hostname == hostname)
|
|
101
|
+
for link in reader.get_tag(feed, autodiscover_tag, ())
|
|
102
|
+
]
|
|
103
|
+
|
|
91
104
|
kwargs = dict(form.data)
|
|
92
105
|
|
|
93
106
|
if not (feed or kwargs.get('starting_after')):
|
|
@@ -106,6 +119,7 @@ def entries():
|
|
|
106
119
|
form=form,
|
|
107
120
|
entries=entries,
|
|
108
121
|
feed=feed,
|
|
122
|
+
autodiscover=autodiscover,
|
|
109
123
|
limit=limit,
|
|
110
124
|
)
|
|
111
125
|
|
|
@@ -277,6 +291,31 @@ def add_feed():
|
|
|
277
291
|
return render_template('add_feed.html', form=form)
|
|
278
292
|
|
|
279
293
|
|
|
294
|
+
@blueprint.route('/feeds/change', methods=['POST'])
|
|
295
|
+
def change_feed_url():
|
|
296
|
+
# NOTE: currently only for post-add autodiscover change,
|
|
297
|
+
# actual "change feed URL" UX needs design (e.g. needs preview)
|
|
298
|
+
|
|
299
|
+
reader = get_reader()
|
|
300
|
+
old = request.form['old']
|
|
301
|
+
new = request.form['new']
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
reader.change_feed_url(old, new)
|
|
305
|
+
except FeedExistsError:
|
|
306
|
+
flash(f"Feed {new} already exists.", 'secondary')
|
|
307
|
+
return redirect(url_for('.entries', feed=old), code=303)
|
|
308
|
+
else:
|
|
309
|
+
# TODO: updating should be out of band
|
|
310
|
+
try:
|
|
311
|
+
reader.update_feed(new)
|
|
312
|
+
except UpdateError:
|
|
313
|
+
pass
|
|
314
|
+
else:
|
|
315
|
+
flash("Changed and updated feed.", 'success')
|
|
316
|
+
return redirect(url_for('.entries', feed=new), code=303)
|
|
317
|
+
|
|
318
|
+
|
|
280
319
|
@blueprint.route('/feeds/import', methods=['GET', 'POST'])
|
|
281
320
|
def import_feeds():
|
|
282
321
|
reader = get_reader()
|
|
@@ -321,6 +360,38 @@ def help():
|
|
|
321
360
|
return render_template('help.html')
|
|
322
361
|
|
|
323
362
|
|
|
363
|
+
@blueprint.route('/settings')
|
|
364
|
+
def settings():
|
|
365
|
+
return render_template('settings.html', exports=get_exports().list())
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@blueprint.route('/settings/exports', methods=['POST'])
|
|
369
|
+
def create_export():
|
|
370
|
+
try:
|
|
371
|
+
get_exports().create()
|
|
372
|
+
except TooManyExportsError:
|
|
373
|
+
flash("Too many exports, delete some of them first.", 'exports.danger')
|
|
374
|
+
return redirect(url_for('.settings', _anchor='exports'), code=303)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@blueprint.route('/settings/exports/<name>')
|
|
378
|
+
def get_export(name):
|
|
379
|
+
exports = get_exports()
|
|
380
|
+
if not exports.exists(name):
|
|
381
|
+
abort(404)
|
|
382
|
+
return send_from_directory(exports.path, name, as_attachment=True)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@blueprint.route('/settings/exports/<name>/delete', methods=['POST'])
|
|
386
|
+
def delete_export(name):
|
|
387
|
+
# flask should ensure <name> doesn't contain slashes etc.
|
|
388
|
+
try:
|
|
389
|
+
get_exports().delete(name)
|
|
390
|
+
except ExportNotFoundError:
|
|
391
|
+
abort(404)
|
|
392
|
+
return redirect(url_for('.settings', _anchor='exports'), code=303)
|
|
393
|
+
|
|
394
|
+
|
|
324
395
|
def stream_template(template_name_or_list, **kwargs):
|
|
325
396
|
# Ensure flashed messages get removed from the session,
|
|
326
397
|
# otherwise they keep adding up and never disappear.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
1
3
|
import click
|
|
4
|
+
import platformdirs
|
|
2
5
|
|
|
6
|
+
from reader._cli import app_name
|
|
3
7
|
from reader._cli import load_reader_config_from_context
|
|
4
8
|
from reader._cli import setup_logging
|
|
5
9
|
|
|
@@ -26,9 +30,16 @@ def make_add_response_headers_middleware(wsgi_app, headers):
|
|
|
26
30
|
help="Import path to a web app plug-in. Can be passed multiple times.",
|
|
27
31
|
)
|
|
28
32
|
@click.option('--legacy/--no-legacy', help="Serve the legacy app.")
|
|
33
|
+
@click.option(
|
|
34
|
+
'--cache-dir',
|
|
35
|
+
type=click.Path(file_okay=False, path_type=pathlib.Path),
|
|
36
|
+
default=platformdirs.user_cache_dir(app_name),
|
|
37
|
+
show_default=True,
|
|
38
|
+
help="The directory to store cache data in.",
|
|
39
|
+
)
|
|
29
40
|
@click.option('-v', '--verbose', count=True)
|
|
30
41
|
@click.pass_context
|
|
31
|
-
def cli(ctx, host, port, plugins, legacy, verbose):
|
|
42
|
+
def cli(ctx, host, port, plugins, legacy, cache_dir, verbose):
|
|
32
43
|
"""Run a local HTTP reader server."""
|
|
33
44
|
|
|
34
45
|
setup_logging(verbose)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from datetime import timezone
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TooManyExportsError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExportNotFoundError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Exports:
|
|
15
|
+
|
|
16
|
+
max_files = 2
|
|
17
|
+
prefix_fmt = "reader.%Y-%m-%d-%H-%M-%S"
|
|
18
|
+
|
|
19
|
+
def __init__(self, reader, path):
|
|
20
|
+
self.reader = reader
|
|
21
|
+
self.path = pathlib.Path(path)
|
|
22
|
+
self.path.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
|
|
24
|
+
def parse_name(self, name):
|
|
25
|
+
name_fmt = self.reader._storage.export_name(self.prefix_fmt)
|
|
26
|
+
try:
|
|
27
|
+
dt = datetime.strptime(name, name_fmt)
|
|
28
|
+
except ValueError:
|
|
29
|
+
return None
|
|
30
|
+
else:
|
|
31
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
32
|
+
|
|
33
|
+
def create(self):
|
|
34
|
+
# not looking at file names accounts for exports in progress
|
|
35
|
+
if len(list(self.path.iterdir())) >= self.max_files:
|
|
36
|
+
raise TooManyExportsError
|
|
37
|
+
prefix = self.reader._now().strftime(self.prefix_fmt)
|
|
38
|
+
self.reader._storage.export(self.path, prefix)
|
|
39
|
+
|
|
40
|
+
def exists(self, name):
|
|
41
|
+
if not self.parse_name(name):
|
|
42
|
+
return False
|
|
43
|
+
return (self.path / name).exists()
|
|
44
|
+
|
|
45
|
+
def list(self):
|
|
46
|
+
rv = []
|
|
47
|
+
for path in self.path.iterdir():
|
|
48
|
+
if dt := self.parse_name(path.name):
|
|
49
|
+
rv.append((path, dt))
|
|
50
|
+
return rv
|
|
51
|
+
|
|
52
|
+
def delete(self, name):
|
|
53
|
+
if not self.parse_name(name):
|
|
54
|
+
raise ExportNotFoundError
|
|
55
|
+
try:
|
|
56
|
+
(self.path / name).unlink()
|
|
57
|
+
except FileNotFoundError:
|
|
58
|
+
raise ExportNotFoundError from None
|
|
@@ -3,6 +3,7 @@ Flask extension that encapsulates configurable reader-related aspects.
|
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import pathlib
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
|
|
8
9
|
from flask import current_app
|
|
@@ -10,13 +11,20 @@ from flask import current_app
|
|
|
10
11
|
from reader import make_reader
|
|
11
12
|
from reader.plugins._loader import PluginLoader
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
from .exports import Exports
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def get_reader():
|
|
17
18
|
return current_app.extensions['reader'].get_reader()
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def get_exports():
|
|
22
|
+
return current_app.extensions['reader'].get_exports()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_plugin_loader = PluginLoader('init_app')
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
class ReaderExtension:
|
|
21
29
|
|
|
22
30
|
def __init__(self, app=None, state_cls=None):
|
|
@@ -26,26 +34,45 @@ class ReaderExtension:
|
|
|
26
34
|
|
|
27
35
|
def init_app(self, app):
|
|
28
36
|
config = app.config['READER']
|
|
29
|
-
app.extensions['reader'] = self.state_cls(
|
|
37
|
+
app.extensions['reader'] = self.state_cls(
|
|
38
|
+
config[''], config.get('web', {}).get('cache_dir')
|
|
39
|
+
)
|
|
30
40
|
_plugin_loader.oneshot(app, config.get('web', {}).get('plugins', ()))
|
|
31
41
|
|
|
32
42
|
|
|
33
43
|
@dataclass
|
|
34
44
|
class ReaderStateBase:
|
|
35
45
|
args: dict
|
|
46
|
+
cache_dir: pathlib.Path | None
|
|
36
47
|
|
|
37
48
|
def get_reader(self):
|
|
38
49
|
raise NotImplementedError
|
|
39
50
|
|
|
51
|
+
def get_user_cache_dir(self):
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
40
54
|
def _make_reader(self, **kwargs):
|
|
41
55
|
return make_reader(**self.args | kwargs)
|
|
42
56
|
|
|
57
|
+
def get_exports(self):
|
|
58
|
+
cache_dir = self.get_user_cache_dir()
|
|
59
|
+
if not cache_dir:
|
|
60
|
+
return None
|
|
61
|
+
return Exports(self.get_reader(), cache_dir / 'exports')
|
|
62
|
+
|
|
43
63
|
|
|
44
64
|
class _ReaderState(ReaderStateBase):
|
|
45
65
|
|
|
46
66
|
def __init__(self, *args, **kwargs):
|
|
47
67
|
super().__init__(*args, **kwargs)
|
|
48
68
|
self._reader = self._make_reader()
|
|
69
|
+
self._exports = super().get_exports()
|
|
49
70
|
|
|
50
71
|
def get_reader(self):
|
|
51
72
|
return self._reader
|
|
73
|
+
|
|
74
|
+
def get_user_cache_dir(self):
|
|
75
|
+
return self.cache_dir
|
|
76
|
+
|
|
77
|
+
def get_exports(self):
|
|
78
|
+
return self._exports
|
|
@@ -69,8 +69,6 @@
|
|
|
69
69
|
height: auto;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
:
|
|
74
|
-
outline: 0;
|
|
75
|
-
box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color);
|
|
72
|
+
h1:target, h2:target, h3:target, h4:target, h5:target, h6:target {
|
|
73
|
+
text-decoration: underline;
|
|
76
74
|
}
|
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
<h1>Add feed</h1>
|
|
15
15
|
|
|
16
|
-
<p>
|
|
16
|
+
<p>
|
|
17
|
+
Paste a feed or website URL:
|
|
18
|
+
<a
|
|
19
|
+
href="{{ url_for('.help', _anchor=help_topic) }}"
|
|
20
|
+
class="link-secondary d-inline-block px-2"
|
|
21
|
+
>
|
|
22
|
+
<i class="bi bi-question-lg" aria-label="help: adding feeds"></i>
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
17
25
|
|
|
18
26
|
<form action="{{ url_for(request.endpoint) }}" method="post" class="my-2 mb-4">
|
|
19
27
|
{#- TODO: aria stuff #}
|