Move theme parser into kitty code
This commit is contained in:
parent
050eb5660d
commit
4a1ca8d582
@ -6,13 +6,17 @@ import datetime
|
|||||||
import http
|
import http
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from typing import Any, Callable, Dict, Match
|
||||||
from urllib.error import HTTPError
|
from urllib.error import HTTPError
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
|
from kitty.config import parse_config
|
||||||
from kitty.constants import cache_dir
|
from kitty.constants import cache_dir
|
||||||
|
from kitty.rgb import Color
|
||||||
|
|
||||||
|
|
||||||
def fetch_themes(
|
def fetch_themes(
|
||||||
@ -49,3 +53,130 @@ def fetch_themes(
|
|||||||
with zipfile.ZipFile(dest_path, 'a') as zf:
|
with zipfile.ZipFile(dest_path, 'a') as zf:
|
||||||
zf.comment = json.dumps({'etag': m.etag, 'timestamp': m.timestamp.isoformat()}).encode('utf-8')
|
zf.comment = json.dumps({'etag': m.etag, 'timestamp': m.timestamp.isoformat()}).encode('utf-8')
|
||||||
return dest_path
|
return dest_path
|
||||||
|
|
||||||
|
|
||||||
|
def zip_file_loader(path_to_zip: str, theme_file_name: str, file_name: str) -> Callable[[], str]:
|
||||||
|
|
||||||
|
name = os.path.join(os.path.dirname(theme_file_name), file_name)
|
||||||
|
|
||||||
|
def zip_loader() -> str:
|
||||||
|
with zipfile.ZipFile(path_to_zip, 'r') as zf, zf.open(name, 'rb') as f:
|
||||||
|
return f.read().decode('utf-8')
|
||||||
|
|
||||||
|
return zip_loader
|
||||||
|
|
||||||
|
|
||||||
|
def theme_name_from_file_name(fname: str) -> str:
|
||||||
|
ans = fname.rsplit('.', 1)[0]
|
||||||
|
ans = ans.replace('_', ' ')
|
||||||
|
|
||||||
|
def camel_case(m: Match) -> str:
|
||||||
|
return str(m.group(1) + ' ' + m.group(2))
|
||||||
|
|
||||||
|
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
|
||||||
|
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
|
||||||
|
|
||||||
|
|
||||||
|
class LineParser:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.in_metadata = False
|
||||||
|
self.in_blurb = False
|
||||||
|
self.keep_going = True
|
||||||
|
|
||||||
|
def __call__(self, line: str, ans: Dict[str, Any]) -> None:
|
||||||
|
is_block = line.startswith('## ')
|
||||||
|
if self.in_metadata and not is_block:
|
||||||
|
self.keep_going = False
|
||||||
|
return
|
||||||
|
if not self.in_metadata and is_block:
|
||||||
|
self.in_metadata = True
|
||||||
|
if not self.in_metadata:
|
||||||
|
return
|
||||||
|
line = line[3:]
|
||||||
|
if self.in_blurb:
|
||||||
|
ans['blurb'] += ' ' + line
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
key, val = line.split(':', 1)
|
||||||
|
except Exception:
|
||||||
|
self.keep_going = False
|
||||||
|
return
|
||||||
|
key = key.strip().lower()
|
||||||
|
val = val.strip()
|
||||||
|
if val:
|
||||||
|
ans[key] = val
|
||||||
|
if key == 'blurb':
|
||||||
|
self.in_blurb = True
|
||||||
|
|
||||||
|
|
||||||
|
def parse_theme(fname: str, raw: str) -> Dict[str, Any]:
|
||||||
|
lines = raw.splitlines()
|
||||||
|
conf = parse_config(lines)
|
||||||
|
bg = conf.get('background', Color())
|
||||||
|
is_dark = max(bg) < 115
|
||||||
|
ans: Dict[str, Any] = {'name': theme_name_from_file_name(fname)}
|
||||||
|
parser = LineParser()
|
||||||
|
for i, line in enumerate(raw.splitlines()):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
parser(line, ans)
|
||||||
|
except Exception as e:
|
||||||
|
raise SystemExit(
|
||||||
|
f'Failed to parse {fname} line {i+1} with error: {e}')
|
||||||
|
if not parser.keep_going:
|
||||||
|
break
|
||||||
|
if is_dark:
|
||||||
|
ans['is_dark'] = True
|
||||||
|
ans['num_settings'] = len(conf) - len(parse_config(()))
|
||||||
|
if ans['num_settings'] < 1:
|
||||||
|
raise SystemExit(f'The theme {fname} has no settings')
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
class Theme:
|
||||||
|
name: str = ''
|
||||||
|
author: str = ''
|
||||||
|
license: str = ''
|
||||||
|
is_dark: bool = False
|
||||||
|
blurb: str = ''
|
||||||
|
num_settings: int = 0
|
||||||
|
|
||||||
|
def apply_dict(self, d: Dict[str, Any]) -> None:
|
||||||
|
self.name = str(d['name'])
|
||||||
|
for x in ('author', 'license', 'blurb'):
|
||||||
|
a = d.get(x)
|
||||||
|
if isinstance(a, str):
|
||||||
|
setattr(self, x, a)
|
||||||
|
for x in ('is_dark', 'num_settings'):
|
||||||
|
a = d.get(x)
|
||||||
|
if isinstance(a, int):
|
||||||
|
setattr(self, x, a)
|
||||||
|
|
||||||
|
def __init__(self, loader: Callable[[], str]):
|
||||||
|
self.loader = loader
|
||||||
|
|
||||||
|
|
||||||
|
class Themes:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.themes: Dict[str, Theme] = {}
|
||||||
|
|
||||||
|
def load_from_zip(self, path_to_zip: str) -> None:
|
||||||
|
with zipfile.ZipFile(path_to_zip, 'r') as zf:
|
||||||
|
for name in zf.namelist():
|
||||||
|
if os.path.basename(name) == 'themes.json':
|
||||||
|
theme_file_name = name
|
||||||
|
with zf.open(theme_file_name, 'rb') as f:
|
||||||
|
items = json.loads(f.read())
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError(f'No themes.json found in {path_to_zip}')
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
t = Theme(zip_file_loader(path_to_zip, theme_file_name, item['file']))
|
||||||
|
t.apply_dict(item)
|
||||||
|
if t.name:
|
||||||
|
self.themes[t.name] = t
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user