From 2db032fea75812260975d6d1b3a8bda329d3b9b1 Mon Sep 17 00:00:00 2001 From: Cody Scott Date: Thu, 25 Jan 2024 18:36:51 -0500 Subject: [PATCH] Fix typehints with mypy --- .github/workflows/python-package.yml | 2 + README.md | 7 ++ htmd/cli.py | 10 ++- htmd/site.py | 100 ++++++++++++------------ htmd/utils.py | 3 +- typehints/csscompressor/__init__.pyi | 4 + typehints/feedwerk/__init__.pyi | 0 typehints/feedwerk/_compat.pyi | 41 ++++++++++ typehints/feedwerk/atom.pyi | 52 ++++++++++++ typehints/flask_flatpages/__init__.pyi | 5 ++ typehints/flask_flatpages/flatpages.pyi | 20 +++++ typehints/flask_flatpages/imports.pyi | 3 + typehints/flask_flatpages/page.pyi | 12 +++ typehints/flask_flatpages/utils.pyi | 11 +++ typehints/flask_frozen/__init__.pyi | 48 ++++++++++++ typehints/htmlmin/__init__.pyi | 4 + typehints/htmlmin/main.pyi | 1 + typehints/jsmin/__init__.pyi | 3 + 18 files changed, 272 insertions(+), 54 deletions(-) create mode 100644 typehints/csscompressor/__init__.pyi create mode 100644 typehints/feedwerk/__init__.pyi create mode 100644 typehints/feedwerk/_compat.pyi create mode 100644 typehints/feedwerk/atom.pyi create mode 100644 typehints/flask_flatpages/__init__.pyi create mode 100644 typehints/flask_flatpages/flatpages.pyi create mode 100644 typehints/flask_flatpages/imports.pyi create mode 100644 typehints/flask_flatpages/page.pyi create mode 100644 typehints/flask_flatpages/utils.pyi create mode 100644 typehints/flask_frozen/__init__.pyi create mode 100644 typehints/htmlmin/__init__.pyi create mode 100644 typehints/htmlmin/main.pyi create mode 100644 typehints/jsmin/__init__.pyi diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a18000b..90c8274 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -11,6 +11,8 @@ jobs: steps: - uses: actions/checkout@v3 - uses: chartboost/ruff-action@v1 + with: + src: "./htmd" build: runs-on: ubuntu-latest strategy: diff --git a/README.md b/README.md index 1830685..3210c5c 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,13 @@ $ ../venv/bin/htmd start $ ../venv/bin/htmd build ``` +### Running mypy + +```shell +$ venv/bin/python -m pip install mypy +$ venv/bin/python -m mypy htmd typehints +``` + ### Running the tests ```shell diff --git a/htmd/cli.py b/htmd/cli.py index 53e9206..396e1c7 100644 --- a/htmd/cli.py +++ b/htmd/cli.py @@ -5,7 +5,7 @@ import click from flask import Flask -from flask_flatpages import FlatPages +from flask_flatpages import FlatPages, Page from .utils import ( combine_and_minify_css, @@ -95,7 +95,7 @@ def verify() -> None: def set_post_time( app: Flask, - post: FlatPages, + post: Page, field: str, date_time: datetime.datetime, ) -> None: @@ -120,7 +120,7 @@ def set_post_time( file.write(line) -def set_posts_datetime(app: Flask, posts: [FlatPages]) -> None: +def set_posts_datetime(app: Flask, posts: FlatPages) -> None: # Ensure each post has a published date # set time for correct date field for post in posts: @@ -170,9 +170,11 @@ def build( app = site.app if css_minify: + assert app.static_folder is not None combine_and_minify_css(Path(app.static_folder)) if js_minify: + assert app.static_folder is not None combine_and_minify_js(Path(app.static_folder)) if css_minify or js_minify: @@ -230,9 +232,11 @@ def preview( app = site.app if css_minify: + assert app.static_folder is not None combine_and_minify_css(Path(app.static_folder)) if js_minify: + assert app.static_folder is not None combine_and_minify_js(Path(app.static_folder)) app.run(debug=True, host=host, port=port) diff --git a/htmd/site.py b/htmd/site.py index 81a1a3a..0c1fd81 100644 --- a/htmd/site.py +++ b/htmd/site.py @@ -3,10 +3,12 @@ from pathlib import Path import sys import tomllib +import typing from bs4 import BeautifulSoup from feedwerk.atom import AtomFeed from flask import abort, Blueprint, Flask, render_template, Response, url_for +from flask.typing import ResponseReturnValue from flask_flatpages import FlatPages, pygments_style_defs from flask_frozen import Freezer from htmlmin import minify @@ -56,41 +58,41 @@ def get_project_dir() -> Path: # Flask configs are flat, config.toml is not # Define the configuration keys and their default values # 'Flask config': [section, key, default] -config_keys = { - 'SITE_NAME': ['site', 'name', ''], - 'SITE_URL': ['site', 'url', ''], - 'SITE_LOGO': ['site', 'logo', ''], - 'SITE_DESCRIPTION': ['site', 'description', ''], - 'SITE_TWITTER': ['site', 'twitter', ''], - 'SITE_FACEBOOK': ['site', 'facebook', ''], - 'FACEBOOK_APP_ID': ['site', 'facebook_app_id', ''], - 'STATIC_FOLDER': ['folders', 'static', 'static'], - 'POSTS_FOLDER': ['folders', 'posts', 'posts'], - 'PAGES_FOLDER': ['folders', 'pages', 'pages'], - 'BUILD_FOLDER': ['folders', 'build', 'build'], - 'POSTS_EXTENSION': ['posts', 'extension', '.md'], - 'PRETTY_HTML': ['html', 'pretty', False], - 'MINIFY_HTML': ['html', 'minify', False], - 'SHOW_AUTHOR': ['author', 'show', True], - 'DEFAULT_AUTHOR': ['author', 'default_name', ''], - 'DEFAULT_AUTHOR_TWITTER': ['author', 'default_twitter', ''], - 'DEFAULT_AUTHOR_FACEBOOK': ['author', 'default_facebook', ''], +config_keys : dict[str, tuple[str, str, typing.Any]] = { + 'SITE_NAME': ('site', 'name', ''), + 'SITE_URL': ('site', 'url', ''), + 'SITE_LOGO': ('site', 'logo', ''), + 'SITE_DESCRIPTION': ('site', 'description', ''), + 'SITE_TWITTER': ('site', 'twitter', ''), + 'SITE_FACEBOOK': ('site', 'facebook', ''), + 'FACEBOOK_APP_ID': ('site', 'facebook_app_id', ''), + 'STATIC_FOLDER': ('folders', 'static', 'static'), + 'POSTS_FOLDER': ('folders', 'posts', 'posts'), + 'PAGES_FOLDER': ('folders', 'pages', 'pages'), + 'BUILD_FOLDER': ('folders', 'build', 'build'), + 'POSTS_EXTENSION': ('posts', 'extension', '.md'), + 'PRETTY_HTML': ('html', 'pretty', False), + 'MINIFY_HTML': ('html', 'minify', False), + 'SHOW_AUTHOR': ('author', 'show', True), + 'DEFAULT_AUTHOR': ('author', 'default_name', ''), + 'DEFAULT_AUTHOR_TWITTER': ('author', 'default_twitter', ''), + 'DEFAULT_AUTHOR_FACEBOOK': ('author', 'default_facebook', ''), } # Update app.config using the configuration keys for flask_key, (table, key, default) in config_keys.items(): app.config[flask_key] = htmd_config.get(table, {}).get(key, default) - +assert app.static_folder is not None # To avoid full paths in config.toml app.config['FLATPAGES_ROOT'] = ( - project_dir / app.config.get('POSTS_FOLDER') + project_dir / app.config['POSTS_FOLDER'] ) app.config['FREEZER_DESTINATION'] = ( - project_dir / app.config.get('BUILD_FOLDER') + project_dir / app.config['BUILD_FOLDER'] ) app.config['FREEZER_REMOVE_EXTRA_FILES'] = False -app.config['FLATPAGES_EXTENSION'] = app.config.get('POSTS_EXTENSION') +app.config['FLATPAGES_EXTENSION'] = app.config['POSTS_EXTENSION'] app.config['INCLUDE_CSS'] = 'combined.min.css' in os.listdir(app.static_folder) app.config['INCLUDE_JS'] = 'combined.min.js' in os.listdir(app.static_folder) @@ -112,9 +114,9 @@ def truncate_post_html(post_html: str) -> str: # Include current htmd site templates -app.jinja_loader = ChoiceLoader([ +app.jinja_loader = ChoiceLoader([ # type: ignore[assignment] FileSystemLoader(project_dir / 'templates/'), - app.jinja_loader, + app.jinja_loader, # type: ignore[list-item] ]) MONTHS = { @@ -135,12 +137,12 @@ def truncate_post_html(post_html: str) -> str: pages = Blueprint( 'pages', __name__, - template_folder=project_dir / app.config.get('PAGES_FOLDER'), + template_folder=project_dir / app.config['PAGES_FOLDER'], ) @app.after_request -def format_html(response: Response) -> Response: +def format_html(response: Response) -> ResponseReturnValue: if response.mimetype == 'text/html': if app.config.get('PRETTY_HTML', False): response.data = BeautifulSoup( @@ -153,7 +155,7 @@ def format_html(response: Response) -> Response: @pages.route('//') -def page(path: str) -> Response: +def page(path: str) -> ResponseReturnValue: try: return render_template(path + '.html', active=path) except TemplateNotFound: @@ -165,18 +167,18 @@ def page(path: str) -> Response: # Will end up in the static directory @app.route('/static/pygments.css') -def pygments_css() -> Response: +def pygments_css() -> ResponseReturnValue: return pygments_style_defs('tango'), 200, {'Content-Type': 'text/css'} @app.route('/') -def index() -> Response: +def index() -> ResponseReturnValue: latest = sorted(posts, reverse=True, key=lambda p: p.meta.get('published')) return render_template('index.html', active='home', posts=latest[:4]) @app.route('/feed.atom') -def feed() -> Response: +def feed() -> ResponseReturnValue: name = app.config.get('SITE_NAME') subtitle = app.config.get('SITE_DESCRIPTION') or 'Recent Blog Posts' url = app.config.get('URL') @@ -209,14 +211,14 @@ def feed() -> Response: @app.route('/all/') -def all_posts() -> Response: +def all_posts() -> ResponseReturnValue: latest = sorted(posts, reverse=True, key=lambda p: p.meta.get('published')) return render_template('all_posts.html', active='posts', posts=latest) # If month and day are ints then Flask removes leading zeros @app.route('/////') -def post(year: str, month: str, day:str, path: str) -> Response: +def post(year: str, month: str, day:str, path: str) -> ResponseReturnValue: if len(year) != 4 or len(month) != 2 or len(day) != 2: # noqa: PLR2004 abort(404) post = posts.get_or_404(path) @@ -227,8 +229,8 @@ def post(year: str, month: str, day:str, path: str) -> Response: @app.route('/tags/') -def all_tags() -> Response: - tag_counts: {str: int} = {} +def all_tags() -> ResponseReturnValue: + tag_counts: dict[str, int] = {} for post in posts: for tag in post.meta.get('tags', []): if tag not in tag_counts: @@ -238,7 +240,7 @@ def all_tags() -> Response: @app.route('/tags//') -def tag(tag: str) -> Response: +def tag(tag: str) -> ResponseReturnValue: tagged = [p for p in posts if tag in p.meta.get('tags', [])] sorted_posts = sorted( tagged, @@ -249,7 +251,7 @@ def tag(tag: str) -> Response: @app.route('/author//') -def author(author: str) -> Response: +def author(author: str) -> ResponseReturnValue: author_posts = [p for p in posts if author == p.meta.get('author', '')] sorted_posts = sorted( author_posts, @@ -265,17 +267,17 @@ def author(author: str) -> Response: @app.route('/404.html') -def not_found() -> Response: +def not_found() -> ResponseReturnValue: return render_template('404.html') @app.route('//') -def year_view(year: int) -> Response: - year = str(year) - if len(year) != len('YYYY'): +def year_view(year: int) -> ResponseReturnValue: + year_str = str(year) + if len(year_str) != len('YYYY'): abort(404) year_posts = [ - p for p in posts if year == p.meta.get('published', []).strftime('%Y') + p for p in posts if year_str == p.meta.get('published', []).strftime('%Y') ] if not year_posts: abort(404) @@ -284,11 +286,11 @@ def year_view(year: int) -> Response: reverse=False, key=lambda p: p.meta.get('published'), ) - return render_template('year.html', year=year, posts=sorted_posts) + return render_template('year.html', year=year_str, posts=sorted_posts) @app.route('///') -def month_view(year: str, month: str) -> Response: +def month_view(year: str, month: str) -> ResponseReturnValue: month_posts = [ p for p in posts if year == p.meta.get('published').strftime('%Y') and month == p.meta.get('published').strftime('%m') @@ -310,7 +312,7 @@ def month_view(year: str, month: str) -> Response: @app.route('////') -def day_view(year: str, month: str, day: str) -> Response: +def day_view(year: str, month: str, day: str) -> ResponseReturnValue: day_posts = [ p for p in posts if year == p.meta.get('published').strftime('%Y') and month == p.meta.get('published').strftime('%m') @@ -329,12 +331,12 @@ def day_view(year: str, month: str, day: str) -> Response: @app.errorhandler(404) -def page_not_found(_e: Exception | int) -> Response: +def page_not_found(_e: Exception | int) -> ResponseReturnValue: return render_template('404.html'), 404 # Telling Frozen-Flask about routes that are not linked to in templates -@freezer.register_generator +@freezer.register_generator # type: ignore[no-redef] def year_view() -> Iterator[dict]: # noqa: F811 for post in posts: yield { @@ -342,7 +344,7 @@ def year_view() -> Iterator[dict]: # noqa: F811 } -@freezer.register_generator +@freezer.register_generator # type: ignore[no-redef] def month_view() -> Iterator[dict]: # noqa: F811 for post in posts: yield { @@ -351,7 +353,7 @@ def month_view() -> Iterator[dict]: # noqa: F811 } -@freezer.register_generator +@freezer.register_generator # type: ignore[no-redef] def day_view() -> Iterator[dict]: # noqa: F811 for post in posts: yield { diff --git a/htmd/utils.py b/htmd/utils.py index ebc7eb2..ac7a2cf 100644 --- a/htmd/utils.py +++ b/htmd/utils.py @@ -1,7 +1,6 @@ from importlib.resources import as_file, files from pathlib import Path import shutil -from typing import BinaryIO import click from csscompressor import compress @@ -67,7 +66,7 @@ def combine_and_minify_js(static_folder: Path) -> None: master.write(jsmin(combined)) -def copy_file(source: BinaryIO, destination: Path) -> None: +def copy_file(source: Path, destination: Path) -> None: if destination.exists() is False: shutil.copyfile(source, destination) click.echo(click.style(f'{destination} was created.', fg='green')) diff --git a/typehints/csscompressor/__init__.pyi b/typehints/csscompressor/__init__.pyi new file mode 100644 index 0000000..13b5cf2 --- /dev/null +++ b/typehints/csscompressor/__init__.pyi @@ -0,0 +1,4 @@ +__all__ = ['compress', 'compress_partitioned'] + +def compress(css: str, max_linelen: int = 0, preserve_exclamation_comments: bool = True) -> str: ... +def compress_partitioned(css: str, max_linelen: int = 0, max_rules_per_file: int = 4000, preserve_exclamation_comments: bool = True) -> list[str]: ... diff --git a/typehints/feedwerk/__init__.pyi b/typehints/feedwerk/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/typehints/feedwerk/_compat.pyi b/typehints/feedwerk/_compat.pyi new file mode 100644 index 0000000..60ef1b8 --- /dev/null +++ b/typehints/feedwerk/_compat.pyi @@ -0,0 +1,41 @@ +from _typeshed import Incomplete +from io import BytesIO as BytesIO, StringIO + +PY2: Incomplete +WIN: Incomplete +unichr = chr +text_type = str +string_types: Incomplete +integer_types: Incomplete +iterkeys: Incomplete +itervalues: Incomplete +iteritems: Incomplete +iterlists: Incomplete +iterlistvalues: Incomplete +int_to_byte: Incomplete +iter_bytes: Incomplete + +def reraise(tp, value, tb: Incomplete | None = None) -> None: ... + +fix_tuple_repr: Incomplete +implements_iterator: Incomplete +implements_to_string: Incomplete +implements_bool: Incomplete +native_string_result: Incomplete +imap = map +izip = zip +ifilter = filter +range_type = range +NativeStringIO = StringIO + +def make_literal_wrapper(reference): ... +def normalize_string_tuple(tup): ... + +try_coerce_native: Incomplete +wsgi_get_bytes: Incomplete + +def wsgi_decoding_dance(s, charset: str = 'utf-8', errors: str = 'replace'): ... +def wsgi_encoding_dance(s, charset: str = 'utf-8', errors: str = 'replace'): ... +def to_bytes(x, charset=..., errors: str = 'strict'): ... +def to_native(x, charset=..., errors: str = 'strict'): ... +def to_unicode(x, charset=..., errors: str = 'strict', allow_none_charset: bool = False): ... diff --git a/typehints/feedwerk/atom.pyi b/typehints/feedwerk/atom.pyi new file mode 100644 index 0000000..865d856 --- /dev/null +++ b/typehints/feedwerk/atom.pyi @@ -0,0 +1,52 @@ +from ._compat import implements_to_string as implements_to_string, string_types as string_types +from _typeshed import Incomplete +from collections.abc import Generator + +XHTML_NAMESPACE: str + +def format_iso8601(obj): ... + +class AtomFeed: + default_generator: Incomplete + title: Incomplete + title_type: Incomplete + url: Incomplete + feed_url: Incomplete + id: Incomplete + updated: Incomplete + author: Incomplete + icon: Incomplete + logo: Incomplete + rights: Incomplete + rights_type: Incomplete + subtitle: Incomplete + subtitle_type: Incomplete + generator: Incomplete + links: Incomplete + entries: Incomplete + def __init__(self, title: Incomplete | None = None, entries: Incomplete | None = None, **kwargs) -> None: ... + def add(self, *args, **kwargs) -> None: ... + def generate(self) -> Generator[Incomplete, None, None]: ... + def to_string(self): ... + def get_response(self): ... + def __call__(self, environ, start_response): ... + +class FeedEntry: + title: Incomplete + title_type: Incomplete + content: Incomplete + content_type: Incomplete + url: Incomplete + id: Incomplete + updated: Incomplete + summary: Incomplete + summary_type: Incomplete + author: Incomplete + published: Incomplete + rights: Incomplete + links: Incomplete + categories: Incomplete + xml_base: Incomplete + def __init__(self, title: Incomplete | None = None, content: Incomplete | None = None, feed_url: Incomplete | None = None, **kwargs) -> None: ... + def generate(self) -> Generator[Incomplete, None, None]: ... + def to_string(self): ... diff --git a/typehints/flask_flatpages/__init__.pyi b/typehints/flask_flatpages/__init__.pyi new file mode 100644 index 0000000..72775db --- /dev/null +++ b/typehints/flask_flatpages/__init__.pyi @@ -0,0 +1,5 @@ +from .flatpages import FlatPages as FlatPages +from .page import Page as Page +from .utils import pygmented_markdown as pygmented_markdown, pygments_style_defs as pygments_style_defs + +__version__: str diff --git a/typehints/flask_flatpages/flatpages.pyi b/typehints/flask_flatpages/flatpages.pyi new file mode 100644 index 0000000..5db9564 --- /dev/null +++ b/typehints/flask_flatpages/flatpages.pyi @@ -0,0 +1,20 @@ +from .page import Page as Page +from .utils import NamedStringIO as NamedStringIO, force_unicode as force_unicode, pygmented_markdown as pygmented_markdown +from _typeshed import Incomplete + +START_TOKENS: Incomplete + +class FlatPages: + default_config: Incomplete + name: Incomplete + config_prefix: str + def __init__(self, app: Incomplete | None = None, name: Incomplete | None = None) -> None: ... + def __iter__(self): ... + def config(self, key): ... + def get(self, path, default: Incomplete | None = None): ... + def get_or_404(self, path): ... + app: Incomplete + def init_app(self, app) -> None: ... + def reload(self) -> None: ... + @property + def root(self): ... diff --git a/typehints/flask_flatpages/imports.pyi b/typehints/flask_flatpages/imports.pyi new file mode 100644 index 0000000..cf0e5b2 --- /dev/null +++ b/typehints/flask_flatpages/imports.pyi @@ -0,0 +1,3 @@ +from pygments.formatters import HtmlFormatter as PygmentsHtmlFormatter + +__all__ = ['PygmentsHtmlFormatter'] diff --git a/typehints/flask_flatpages/page.pyi b/typehints/flask_flatpages/page.pyi new file mode 100644 index 0000000..5899ef8 --- /dev/null +++ b/typehints/flask_flatpages/page.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete + +class Page: + path: Incomplete + body: Incomplete + html_renderer: Incomplete + folder: Incomplete + def __init__(self, path, meta, body, html_renderer, folder) -> None: ... + def __getitem__(self, name): ... + def __html__(self): ... + def html(self): ... + def meta(self): ... diff --git a/typehints/flask_flatpages/utils.pyi b/typehints/flask_flatpages/utils.pyi new file mode 100644 index 0000000..7ade7a1 --- /dev/null +++ b/typehints/flask_flatpages/utils.pyi @@ -0,0 +1,11 @@ +from .imports import PygmentsHtmlFormatter as PygmentsHtmlFormatter +from _typeshed import Incomplete +from io import StringIO + +class NamedStringIO(StringIO): + name: Incomplete + def __init__(self, content, name) -> None: ... + +def force_unicode(value, encoding: str = 'utf-8', errors: str = 'strict'): ... +def pygmented_markdown(text, flatpages: Incomplete | None = None): ... +def pygments_style_defs(style: str = 'default'): ... diff --git a/typehints/flask_frozen/__init__.pyi b/typehints/flask_frozen/__init__.pyi new file mode 100644 index 0000000..1547d12 --- /dev/null +++ b/typehints/flask_frozen/__init__.pyi @@ -0,0 +1,48 @@ +from _typeshed import Incomplete +from collections.abc import Generator +import types +from typing import NamedTuple + + +__all__ = ['Freezer', 'walk_directory', 'relative_url_for'] + +class FrozenFlaskWarning(Warning): ... +class MissingURLGeneratorWarning(FrozenFlaskWarning): ... +class MimetypeMismatchWarning(FrozenFlaskWarning): ... +class NotFoundWarning(FrozenFlaskWarning): ... +class RedirectWarning(FrozenFlaskWarning): ... + +class Page(NamedTuple): + url: Incomplete + path: Incomplete + +class Freezer: + url_generators: Incomplete + log_url_for: Incomplete + def __init__(self, app: Incomplete | None = None, with_static_files: bool = True, with_no_argument_rules: bool = True, log_url_for: bool = True) -> None: ... + app: Incomplete + url_for_logger: Incomplete + def init_app(self, app) -> None: ... + def register_generator(self, function): ... + @property + def root(self): ... + def freeze_yield(self) -> Generator[Incomplete, None, None]: ... + def freeze(self): ... + def all_urls(self) -> Generator[Incomplete, None, None]: ... + def urlpath_to_filepath(self, path): ... + def serve(self, **options) -> None: ... + def run(self, **options) -> None: ... + def make_static_app(self): ... + def static_files_urls(self) -> Generator[Incomplete, None, None]: ... + def no_argument_rules_urls(self) -> Generator[Incomplete, None, None]: ... + +def walk_directory(root, ignore=()) -> Generator[Incomplete, None, None]: ... +def relative_url_for(endpoint, **values): ... + +class UrlForLogger: + app: Incomplete + logged_calls: Incomplete + def __init__(self, app) -> None: ... + def __enter__(self) -> None: ... + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: types.TracebackType | None) -> None: ... + def iter_calls(self) -> Generator[Incomplete, None, None]: ... diff --git a/typehints/htmlmin/__init__.pyi b/typehints/htmlmin/__init__.pyi new file mode 100644 index 0000000..1655d19 --- /dev/null +++ b/typehints/htmlmin/__init__.pyi @@ -0,0 +1,4 @@ +from .main import minify + +__all__ = ['minify'] +__version__: str diff --git a/typehints/htmlmin/main.pyi b/typehints/htmlmin/main.pyi new file mode 100644 index 0000000..823fca9 --- /dev/null +++ b/typehints/htmlmin/main.pyi @@ -0,0 +1 @@ +def minify(input: str, remove_comments: bool = False, remove_empty_space: bool = False, remove_all_empty_space: bool = False, reduce_empty_attributes: bool = True, reduce_boolean_attributes: bool = False, remove_optional_attribute_quotes: bool = True, convert_charrefs: bool = True, keep_pre: bool = False, pre_tags=..., pre_attr: str = 'pre', cls=...) -> str: ... diff --git a/typehints/jsmin/__init__.pyi b/typehints/jsmin/__init__.pyi new file mode 100644 index 0000000..3764ac2 --- /dev/null +++ b/typehints/jsmin/__init__.pyi @@ -0,0 +1,3 @@ +__all__ = ['jsmin'] + +def jsmin(js: str, **kwargs) -> str: ...