Add a non-interactive mode to the themes kitten

This commit is contained in:
Kovid Goyal 2021-08-08 07:57:14 +05:30
parent e1ed9aca10
commit d916ecc4f3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 78 additions and 9 deletions

View File

@ -47,3 +47,5 @@ go to `kitty-themes <https://github.com/kovidgoyal/kitty-themes>`_ and open a pu
asking to add your contributions to the repository. Use the file asking to add your contributions to the repository. Use the file
`template.conf <https://github.com/kovidgoyal/kitty-themes/raw/master/template.conf>`_ as `template.conf <https://github.com/kovidgoyal/kitty-themes/raw/master/template.conf>`_ as
a template when creating your theme. a template when creating your theme.
.. include:: ../generated/cli-kitten-themes.rst

View File

@ -75,11 +75,17 @@ def set_comment_in_zip_file(path: str, data: str) -> None:
zf.comment = data.encode('utf-8') zf.comment = data.encode('utf-8')
class NoCacheFound(ValueError):
pass
def fetch_themes( def fetch_themes(
name: str = 'kitty-themes', name: str = 'kitty-themes',
url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master' url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master',
cache_age: float = 1,
) -> str: ) -> str:
now = datetime.datetime.now(datetime.timezone.utc) now = datetime.datetime.now(datetime.timezone.utc)
cache_age_delta = datetime.timedelta(days=cache_age)
class Metadata: class Metadata:
def __init__(self) -> None: def __init__(self) -> None:
@ -95,8 +101,11 @@ def fetch_themes(
q = json.loads(zf.comment) q = json.loads(zf.comment)
m.etag = str(q.get('etag') or '') m.etag = str(q.get('etag') or '')
m.timestamp = datetime.datetime.fromisoformat(q['timestamp']) m.timestamp = datetime.datetime.fromisoformat(q['timestamp'])
if (now - m.timestamp).days < 1: if cache_age < 0 or (now - m.timestamp) < cache_age_delta:
return dest_path return dest_path
if cache_age < 0:
raise NoCacheFound('No local themes cache found and negative cache age specified, aborting')
rq = Request(url) rq = Request(url)
m.timestamp = now m.timestamp = now
if m.etag: if m.etag:
@ -348,9 +357,9 @@ class Themes:
self.index_map = tuple(self.themes) self.index_map = tuple(self.themes)
def load_themes() -> Themes: def load_themes(cache_age: float = 1.) -> Themes:
ans = Themes() ans = Themes()
ans.load_from_zip(fetch_themes()) ans.load_from_zip(fetch_themes(cache_age=cache_age))
ans.load_from_dir(os.path.join(config_dir, 'themes')) ans.load_from_dir(os.path.join(config_dir, 'themes'))
ans.index_map = tuple(ans.themes) ans.index_map = tuple(ans.themes)
return ans return ans

View File

@ -7,11 +7,13 @@ import re
import sys import sys
import traceback import traceback
from enum import Enum, auto from enum import Enum, auto
from gettext import gettext as _
from typing import ( from typing import (
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
) )
from kitty.cli import create_default_opts from kitty.cli import create_default_opts, parse_args
from kitty.cli_stub import ThemesCLIOptions
from kitty.config import cached_values_for from kitty.config import cached_values_for
from kitty.constants import config_dir from kitty.constants import config_dir
from kitty.fast_data_types import truncate_point_for_length, wcswidth from kitty.fast_data_types import truncate_point_for_length, wcswidth
@ -23,7 +25,7 @@ from ..tui.handler import Handler
from ..tui.line_edit import LineEdit from ..tui.line_edit import LineEdit
from ..tui.loop import Loop from ..tui.loop import Loop
from ..tui.operations import color_code, styled from ..tui.operations import color_code, styled
from .collection import MARK_AFTER, Theme, Themes, load_themes from .collection import MARK_AFTER, NoCacheFound, Theme, Themes, load_themes
separator = '' separator = ''
@ -133,8 +135,9 @@ class ThemesList:
class ThemesHandler(Handler): class ThemesHandler(Handler):
def __init__(self, cached_values: Dict[str, Any]) -> None: def __init__(self, cached_values: Dict[str, Any], cli_opts: ThemesCLIOptions) -> None:
self.cached_values = cached_values self.cached_values = cached_values
self.cli_opts = cli_opts
self.state = State.fetching self.state = State.fetching
self.report_traceback_on_exit: Optional[str] = None self.report_traceback_on_exit: Optional[str] = None
self.filter_map: Dict[str, Callable[[Theme], bool]] = { self.filter_map: Dict[str, Callable[[Theme], bool]] = {
@ -221,7 +224,7 @@ class ThemesHandler(Handler):
def fetch() -> None: def fetch() -> None:
try: try:
themes: Union[Themes, str] = load_themes() themes: Union[Themes, str] = load_themes(self.cli_opts.cache_age)
except Exception: except Exception:
themes = format_traceback('Failed to download themes') themes = format_traceback('Failed to download themes')
self.asyncio_loop.call_soon_threadsafe(fetching_done, themes) self.asyncio_loop.call_soon_threadsafe(fetching_done, themes)
@ -502,10 +505,56 @@ class ThemesHandler(Handler):
self.quit_loop(1) self.quit_loop(1)
help_text = (
'Change the kitty theme. If no theme name is supplied, run interactively, otherwise'
' change the current theme to the specified theme name.'
)
usage = '[theme name to switch to]'
OPTIONS = '''
--cache-age
type=float
default=1
Check for new themes only after the specified number of days. A value of
zero will always check for new themes. A negative value will never check
for new themes, instead raising an error if a local copy of the themes
is not available.
'''.format
def parse_themes_args(args: List[str]) -> Tuple[ThemesCLIOptions, List[str]]:
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten themes', result_class=ThemesCLIOptions)
def non_interactive(cli_opts: ThemesCLIOptions, theme_name: str) -> None:
try:
themes = load_themes(cli_opts.cache_age)
except NoCacheFound as e:
raise SystemExit(str(e))
try:
theme = themes[theme_name]
except KeyError:
raise SystemExit(f'No theme named: {theme_name}')
theme.save_in_conf(config_dir)
def main(args: List[str]) -> None: def main(args: List[str]) -> None:
try:
cli_opts, items = parse_themes_args(args[1:])
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return None
if len(items) > 1:
raise SystemExit('At most one theme name must be specified')
if len(items) == 1:
return non_interactive(cli_opts, items[0])
loop = Loop() loop = Loop()
with cached_values_for('themes-kitten') as cached_values: with cached_values_for('themes-kitten') as cached_values:
handler = ThemesHandler(cached_values) handler = ThemesHandler(cached_values, cli_opts)
loop.loop(handler) loop.loop(handler)
if loop.return_code != 0: if loop.return_code != 0:
if handler.report_traceback_on_exit: if handler.report_traceback_on_exit:
@ -521,3 +570,8 @@ def main(args: List[str]) -> None:
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv) main(sys.argv)
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text

View File

@ -14,6 +14,7 @@ LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOpt
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
ThemesCLIOptions = CLIOptions
def generate_stub() -> None: def generate_stub() -> None:
@ -72,6 +73,9 @@ def generate_stub() -> None:
from kittens.unicode_input.main import OPTIONS from kittens.unicode_input.main import OPTIONS
do(OPTIONS(), 'UnicodeCLIOptions') do(OPTIONS(), 'UnicodeCLIOptions')
from kittens.themes.main import OPTIONS
do(OPTIONS(), 'ThemesCLIOptions')
from kitty.rc.base import all_command_names, command_for_name from kitty.rc.base import all_command_names, command_for_name
for cmd_name in all_command_names(): for cmd_name in all_command_names():
cmd = command_for_name(cmd_name) cmd = command_for_name(cmd_name)