Improving MIME type detection for some common file types when they are missing from the system MIME database

Also allow the user to specify their own database via mime.types in the
kitty config directory. See #3056
This commit is contained in:
Kovid Goyal 2020-10-25 13:42:11 +05:30
parent e160cbf32b
commit 75a94bcd96
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 62 additions and 13 deletions

View File

@ -24,6 +24,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Fix selections created by dragging upwards not being auto-cleared when - Fix selections created by dragging upwards not being auto-cleared when
screen contents change (:pull:`3028`) screen contents change (:pull:`3028`)
- Allow adding MIME definitions to kitty by placing a ``mime.types`` file in
the kitty config directory (:iss:`3056`)
0.19.1 [2020-10-06] 0.19.1 [2020-10-06]
------------------- -------------------

View File

@ -81,7 +81,11 @@ lines. The various available criteria are:
``mime`` ``mime``
A comma separated list of MIME types, for example: ``text/*, image/*, A comma separated list of MIME types, for example: ``text/*, image/*,
application/pdf`` application/pdf``. You can add MIME types to kitty by creating the
:file:`mime.types` in the kitty configuration directory. Useful if your
system MIME database does not have definitions you need. This file is
in the standard format of one definition per line, like: ``text/plain rst
md``.
``ext`` ``ext``
A comma separated list of file extensions, for example: ``jpeg, tar.gz`` A comma separated list of file extensions, for example: ``jpeg, tar.gz``

View File

@ -7,7 +7,7 @@ import re
from contextlib import suppress from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from hashlib import md5 from hashlib import md5
from mimetypes import guess_type from kitty.guess_mime_type import guess_type
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
if TYPE_CHECKING: if TYPE_CHECKING:
@ -136,7 +136,7 @@ def sanitize(text: str) -> str:
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)
def mime_type_for_path(path: str) -> str: def mime_type_for_path(path: str) -> str:
return guess_type(path)[0] or 'application/octet-stream' return guess_type(path) or 'application/octet-stream'
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)

View File

@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import contextlib import contextlib
import mimetypes
import os import os
import re import re
import socket import socket
@ -18,6 +17,7 @@ from typing import (
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
) )
from kitty.guess_mime_type import guess_type
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname from kitty.constants import appname
@ -273,7 +273,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
def scan(d: str) -> Generator[Tuple[str, str], None, None]: def scan(d: str) -> Generator[Tuple[str, str], None, None]:
for dirpath, dirnames, filenames in os.walk(d): for dirpath, dirnames, filenames in os.walk(d):
for f in filenames: for f in filenames:
mt = mimetypes.guess_type(f)[0] mt = guess_type(f)
if mt and mt.startswith('image/'): if mt and mt.startswith('image/'):
yield os.path.join(dirpath, f), mt yield os.path.join(dirpath, f), mt

View File

@ -394,10 +394,10 @@ def complete_files_and_dirs(
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None: def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from mimetypes import guess_type from .guess_mime_type import guess_type
def icat_file_predicate(filename: str) -> bool: def icat_file_predicate(filename: str) -> bool:
mt = guess_type(filename)[0] mt = guess_type(filename)
if mt and mt.startswith('image/'): if mt and mt.startswith('image/'):
return True return True
return False return False

45
kitty/guess_mime_type.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
from contextlib import suppress
from typing import Optional
known_extensions = {
'asciidoc': 'text/asciidoctor',
'conf': 'text/config',
'md': 'text/markdown',
'pyj': 'text/rapydscript-ng',
'recipe': 'text/python',
'rst': 'text/restructured-text',
'toml': 'text/toml',
'vim': 'text/vim',
'yaml': 'text/yaml',
}
def is_rc_file(path: str) -> bool:
name = os.path.basename(path)
return '.' not in name and name.endswith('rc')
def guess_type(path: str) -> Optional[str]:
if not hasattr(guess_type, 'inited'):
setattr(guess_type, 'inited', True)
from mimetypes import init
init(None)
from kitty.constants import config_dir
local_defs = os.path.join(config_dir, 'mime.types')
if os.path.exists(local_defs):
init((local_defs,))
from mimetypes import guess_type as stdlib_guess_type
mt = None
with suppress(Exception):
mt = stdlib_guess_type(path)[0]
if not mt:
ext = path.rpartition('.')[-1].lower()
mt = known_extensions.get(ext)
if not mt and is_rc_file(path):
mt = 'text/plain'
return mt

View File

@ -17,6 +17,7 @@ from .config import KeyAction, parse_key_action
from .constants import config_dir from .constants import config_dir
from .typing import MatchType from .typing import MatchType
from .utils import expandvars, log_error from .utils import expandvars, log_error
from .guess_mime_type import guess_type
class MatchCriteria(NamedTuple): class MatchCriteria(NamedTuple):
@ -75,12 +76,8 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
if mc.type == 'mime': if mc.type == 'mime':
import fnmatch import fnmatch
from mimetypes import guess_type mt = guess_type(unquoted_path)
try: if not mt:
mt = guess_type(unquoted_path)[0]
except Exception:
return False
if mt is None:
return False return False
mt = mt.lower() mt = mt.lower()
for mpat in mc.value.split(','): for mpat in mc.value.split(','):