mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
feat(client) add client
This commit is contained in:
11 files changed
+342
No files matched your search
@@ -0,0 +1,31 @@
|
||||
class Ansi:
|
||||
HEADER = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
END = '\033[0m'
|
||||
|
||||
def disable(self) -> None:
|
||||
self.HEADER = ''
|
||||
self.BLUE = ''
|
||||
self.GREEN = ''
|
||||
self.WARNING = ''
|
||||
self.FAIL = ''
|
||||
self.BOLD = ''
|
||||
self.UNDERLINE = ''
|
||||
self.END = ''
|
||||
|
||||
@staticmethod
|
||||
def blend(color: list[str], text: str) -> str:
|
||||
return f"{"".join(color)}{text}{Ansi.END}"
|
||||
|
||||
@staticmethod
|
||||
def bold(text: str) -> str:
|
||||
return f"{Ansi.BOLD}{text}{Ansi.END}"
|
||||
|
||||
@staticmethod
|
||||
def underline(text: str) -> str:
|
||||
return f"{Ansi.UNDERLINE}{text}{Ansi.END}"
|
||||
@@ -0,0 +1,46 @@
|
||||
from collections.abc import Callable
|
||||
from typing import List, Iterable
|
||||
|
||||
import prompt_toolkit
|
||||
from prompt_toolkit.completion import Completer, CompleteEvent, Completion
|
||||
from prompt_toolkit.document import Document
|
||||
from pypinyin import lazy_pinyin, Style
|
||||
|
||||
|
||||
class AutoCompleter(Completer):
|
||||
def __init__(self, candidates: List[str], matching_algorithm: Callable[[str, str], bool]):
|
||||
self.candidates = candidates
|
||||
self.matching_algorithm = matching_algorithm
|
||||
|
||||
def get_completions(self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]:
|
||||
curr_word = document.text_before_cursor
|
||||
for c in self.candidates:
|
||||
if self.matching_algorithm(curr_word, c):
|
||||
yield Completion(c, start_position=-len(curr_word))
|
||||
|
||||
|
||||
def exact_matcher(word: str, candidate: str, case_sensitive: bool = False) -> bool:
|
||||
if case_sensitive:
|
||||
return word == candidate
|
||||
else:
|
||||
return word.lower() == candidate.lower()
|
||||
|
||||
|
||||
def fuzzy_matcher(word: str, candidate: str) -> bool:
|
||||
return word.lower() in candidate.lower()
|
||||
|
||||
|
||||
def fuzzy_pinyin_matcher(word: str, candidate: str) -> bool:
|
||||
try:
|
||||
word_matcher = ''.join(lazy_pinyin(word, style=Style.NORMAL))
|
||||
candidate_matcher = ''.join(lazy_pinyin(candidate, style=Style.NORMAL))
|
||||
return candidate_matcher.startswith(word_matcher)
|
||||
|
||||
except e:
|
||||
return fuzzy_matcher(word, candidate)
|
||||
|
||||
|
||||
def input_autocompletion(prompt: str, candidates: List[str],
|
||||
matching_algorithm: Callable[[str, str], bool] = exact_matcher) -> str:
|
||||
completer = AutoCompleter(candidates, matching_algorithm)
|
||||
return prompt_toolkit.prompt(prompt, completer=completer, complete_in_thread=True)
|
||||
@@ -0,0 +1,49 @@
|
||||
import json
|
||||
import os
|
||||
from typing import TextIO
|
||||
|
||||
CACHE_DIR = ".cache"
|
||||
ITEMS = {"register": "register.json"}
|
||||
|
||||
|
||||
def overwrite_json_file(file: TextIO, json_data: dict):
|
||||
file.seek(0)
|
||||
file.write(json.dumps(json_data, indent=4, ensure_ascii=False))
|
||||
file.truncate()
|
||||
|
||||
|
||||
def init_cache():
|
||||
if not os.path.exists(CACHE_DIR):
|
||||
os.makedirs(CACHE_DIR)
|
||||
for item in ITEMS.values():
|
||||
item_path = os.path.join(CACHE_DIR, item)
|
||||
if not os.path.exists(item_path):
|
||||
with open(item_path, 'w') as cache_file:
|
||||
match item:
|
||||
case "register.json":
|
||||
overwrite_json_file(cache_file, {})
|
||||
break
|
||||
|
||||
|
||||
def write_tags_cache(tags: list[str]):
|
||||
with open(os.path.join(CACHE_DIR, ITEMS["register"]), 'r+') as tags_cache_file:
|
||||
cache = json.load(tags_cache_file)
|
||||
cache["tags"] = tags
|
||||
overwrite_json_file(tags_cache_file, cache)
|
||||
|
||||
|
||||
def write_category_cache(categories: list[str]):
|
||||
with open(os.path.join(CACHE_DIR, ITEMS["register"]), 'r+') as categories_cache_file:
|
||||
cache = json.load(categories_cache_file)
|
||||
cache["categories"] = categories
|
||||
overwrite_json_file(categories_cache_file, cache)
|
||||
|
||||
|
||||
def get_tags_cache() -> list[str]:
|
||||
with open(os.path.join(CACHE_DIR, ITEMS["register"]), 'r') as tags_cache_file:
|
||||
return json.load(tags_cache_file)["tags"]
|
||||
|
||||
|
||||
def get_categories_cache() -> list[str]:
|
||||
with open(os.path.join(CACHE_DIR, ITEMS["register"]), 'r') as categories_cache_file:
|
||||
return json.load(categories_cache_file)["categories"]
|
||||
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
|
||||
import frontmatter
|
||||
|
||||
from utils.cache import write_tags_cache, write_category_cache
|
||||
from utils.lang import list_exclude
|
||||
|
||||
EXCLUDE = [".DS_Store"]
|
||||
POSTS_DIR = "content/posts"
|
||||
|
||||
|
||||
def build_post_idx():
|
||||
tags = set()
|
||||
categories = set()
|
||||
for year in list_exclude(os.listdir(POSTS_DIR), EXCLUDE):
|
||||
for post_dir in list_exclude(os.listdir(os.path.join(POSTS_DIR, year)), EXCLUDE):
|
||||
target_file = os.path.join(POSTS_DIR, year, post_dir, "index.md")
|
||||
with open(target_file, "r") as f:
|
||||
post = frontmatter.loads(f.read())
|
||||
try:
|
||||
for tag in post.metadata["tags"]:
|
||||
tags.add(tag)
|
||||
categories.add(post.metadata["category"])
|
||||
except KeyError:
|
||||
print(f"failed to parse {target_file}")
|
||||
write_tags_cache(list(tags))
|
||||
write_category_cache(list(categories))
|
||||
@@ -0,0 +1,13 @@
|
||||
from utils.ansi import Ansi
|
||||
|
||||
|
||||
def list_exclude(source: list, exclude: list):
|
||||
return filter(lambda item: item not in exclude, source)
|
||||
|
||||
|
||||
def eprint(msg: str):
|
||||
print(Ansi.blend([Ansi.FAIL], "error: ") + msg)
|
||||
|
||||
|
||||
def wprint(msg: str):
|
||||
print(Ansi.blend([Ansi.WARNING], "warn: ") + msg)
|
||||
Reference in new issue
Block a user