xython 4.6.4__tar.gz → 4.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xython-4.6.4/src/xython.egg-info → xython-4.7.1}/PKG-INFO +1 -1
- {xython-4.6.4 → xython-4.7.1}/pyproject.toml +1 -1
- {xython-4.6.4 → xython-4.7.1}/src/xython/__init__.py +3 -3
- xython-4.7.1/src/xython/alias_installer.py +119 -0
- xython-4.7.1/src/xython/locales_loader.py +53 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_auto.py +69 -683
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_chrome.py +17 -1
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_color.py +772 -836
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_common.py +498 -478
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_db.py +28 -89
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_edge.py +101 -721
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_excel.py +1925 -2703
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_hwp.py +63 -470
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_list.py +24 -71
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_map.py +23 -49
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_outlook.py +434 -474
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_re.py +278 -532
- xython-4.7.1/src/xython/xy_time.py +3824 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_util.py +701 -71
- xython-4.7.1/src/xython/xy_word.py +6265 -0
- {xython-4.6.4 → xython-4.7.1/src/xython.egg-info}/PKG-INFO +1 -1
- {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/SOURCES.txt +2 -0
- xython-4.6.4/src/xython/xy_time.py +0 -5915
- xython-4.6.4/src/xython/xy_word.py +0 -6707
- {xython-4.6.4 → xython-4.7.1}/MANIFEST.in +0 -0
- {xython-4.6.4 → xython-4.7.1}/README.md +0 -0
- {xython-4.6.4 → xython-4.7.1}/setup.cfg +0 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_excel_event.py +0 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython/xy_manual.py +0 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/dependency_links.txt +0 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/requires.txt +0 -0
- {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# __init__.py
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2025 (sjpkorea@naver.com) and contributors
|
|
3
|
+
|
|
3
4
|
|
|
4
|
-
#https://blog.naver.com/xython
|
|
5
5
|
import inspect
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
-
__version__ = '4.
|
|
9
|
+
__version__ = '4.3.5' #1
|
|
10
10
|
real_path = os.path.dirname(os.path.abspath(__file__)).replace("\\","/") #2
|
|
11
11
|
sys.path.append(real_path)
|
|
12
12
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
alias_installer.py (개선판 - 각 모듈의 호출부는 그대로 두면 됨)
|
|
4
|
+
|
|
5
|
+
방식: 인스턴스마다 alias 를 setattr 하지 않고, __getattr__ 에서 '처음 쓸 때' 만든다.
|
|
6
|
+
- 인스턴스 생성이 가볍다 (280개 wrapper 를 매번 만들지 않음)
|
|
7
|
+
- alias -> 원본 역방향 표를 (모듈, 언어) 단위로 1번만 만들고 캐시
|
|
8
|
+
- 만든 wrapper 는 클래스 단위로 캐시하고, 인스턴스에는 바인딩만 한다
|
|
9
|
+
(인스턴스 __dict__ 가 깨끗해서 pickle / copy.deepcopy 도 안전)
|
|
10
|
+
- functools.wraps 로 원본 시그니처 유지 -> help(), IDE 에서 인자 목록이 보인다
|
|
11
|
+
- make_dir_fallback 을 쓰면 dir()/탭 자동완성에도 alias 가 보인다
|
|
12
|
+
"""
|
|
13
|
+
import difflib, types
|
|
14
|
+
import functools
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
|
|
17
|
+
from .locales_loader import load_locales_for_module
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _normalize_langs(lang):
|
|
21
|
+
if lang is None:
|
|
22
|
+
return ()
|
|
23
|
+
if isinstance(lang, str):
|
|
24
|
+
return (lang,)
|
|
25
|
+
return tuple(lang)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_combined_doc(orig_name, locales, active_langs):
|
|
29
|
+
"""여러 언어의 doc 을 순서대로 이어 붙인 docstring 생성."""
|
|
30
|
+
lines = []
|
|
31
|
+
for lang in active_langs:
|
|
32
|
+
doc = locales.get(lang, {}).get("docs", {}).get(orig_name)
|
|
33
|
+
if doc:
|
|
34
|
+
lines.append(f"[{lang}] {doc}")
|
|
35
|
+
return "\n".join(lines) if lines else None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@lru_cache(maxsize=None)
|
|
39
|
+
def _alias_table(module_name, langs):
|
|
40
|
+
"""(alias -> 원본이름) 역방향 표와 locales 를 반환. langs 는 tuple."""
|
|
41
|
+
locales = load_locales_for_module(module_name, langs)
|
|
42
|
+
reverse = {}
|
|
43
|
+
for lg in langs:
|
|
44
|
+
for orig, alias in locales[lg]["aliases"].items():
|
|
45
|
+
if alias and alias != orig: # 빈 문자열(미번역)은 무시
|
|
46
|
+
reverse.setdefault(alias, orig) # 충돌 시 앞쪽 언어가 우선
|
|
47
|
+
return reverse, locales
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@lru_cache(maxsize=None)
|
|
51
|
+
def _make_alias_function(owner_cls, module_name, orig_name, alias_name, langs):
|
|
52
|
+
orig_func = getattr(owner_cls, orig_name, None)
|
|
53
|
+
if orig_func is None or not callable(orig_func):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
_, locales = _alias_table(module_name, langs)
|
|
57
|
+
doc = build_combined_doc(orig_name, locales, langs) or orig_func.__doc__
|
|
58
|
+
|
|
59
|
+
@functools.wraps(orig_func)
|
|
60
|
+
def wrapper(self, *args, **kwargs):
|
|
61
|
+
return orig_func(self, *args, **kwargs)
|
|
62
|
+
|
|
63
|
+
wrapper.__name__ = alias_name
|
|
64
|
+
wrapper.__qualname__ = f"{owner_cls.__name__}.{alias_name}"
|
|
65
|
+
wrapper.__doc__ = doc
|
|
66
|
+
return wrapper
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# alias_installer.py
|
|
70
|
+
def install_instance_aliases(instance, module_name, active_langs):
|
|
71
|
+
langs = _normalize_langs(active_langs)
|
|
72
|
+
reverse, _ = _alias_table(module_name, langs) # JSON 캐시 데움
|
|
73
|
+
owner_cls = type(instance)
|
|
74
|
+
for alias_name in reverse:
|
|
75
|
+
# 클래스에 이미 있으면(@alias 로 등록된 것) 건너뜀 → 충돌 방지
|
|
76
|
+
if hasattr(owner_cls, alias_name):
|
|
77
|
+
continue
|
|
78
|
+
func = _make_alias_function(owner_cls, module_name,
|
|
79
|
+
reverse[alias_name], alias_name, langs)
|
|
80
|
+
if func is not None:
|
|
81
|
+
# 인스턴스에 바인딩 (인스턴스마다 wrapper 1개씩 생김 — 비용 있음)
|
|
82
|
+
setattr(instance, alias_name, types.MethodType(func, instance))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def make_getattr_fallback(module_name):
|
|
86
|
+
"""클래스에 `__getattr__ = make_getattr_fallback("xy_time")` 로 붙인다."""
|
|
87
|
+
def _getattr(self, name):
|
|
88
|
+
if name.startswith("__") and name.endswith("__"):
|
|
89
|
+
raise AttributeError(name)
|
|
90
|
+
|
|
91
|
+
langs = _normalize_langs(self.__dict__.get("lang"))
|
|
92
|
+
msg = f"'{type(self).__name__}' object has no attribute '{name}'"
|
|
93
|
+
|
|
94
|
+
if langs:
|
|
95
|
+
reverse, _ = _alias_table(module_name, langs)
|
|
96
|
+
orig = reverse.get(name)
|
|
97
|
+
if orig is not None:
|
|
98
|
+
func = _make_alias_function(type(self), module_name, orig, name, langs)
|
|
99
|
+
if func is not None:
|
|
100
|
+
return func.__get__(self, type(self))
|
|
101
|
+
close = difflib.get_close_matches(name, reverse, n=1)
|
|
102
|
+
if close:
|
|
103
|
+
msg += f". Did you mean '{close[0]}'?"
|
|
104
|
+
|
|
105
|
+
raise AttributeError(msg)
|
|
106
|
+
|
|
107
|
+
return _getattr
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def make_dir_fallback(module_name):
|
|
111
|
+
"""(선택) 클래스에 `__dir__ = make_dir_fallback("xy_time")` 를 붙이면 자동완성에 alias 가 보인다."""
|
|
112
|
+
def _dir(self):
|
|
113
|
+
names = set(object.__dir__(self))
|
|
114
|
+
langs = _normalize_langs(self.__dict__.get("lang"))
|
|
115
|
+
if langs:
|
|
116
|
+
names.update(_alias_table(module_name, langs)[0])
|
|
117
|
+
return sorted(names)
|
|
118
|
+
|
|
119
|
+
return _dir
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
locales_loader.py
|
|
4
|
+
|
|
5
|
+
locales/{lang}/{module_name}.json 을 읽어서 dict 로 반환.
|
|
6
|
+
- aliases : {원본함수명: 언어별이름}
|
|
7
|
+
- docs : {원본함수명: 언어별설명}
|
|
8
|
+
"""
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
_LOCALES_DIR = os.path.join(os.path.dirname(__file__), "locales")
|
|
13
|
+
_cache = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_locale(module_name, lang):
|
|
17
|
+
key = (module_name, lang)
|
|
18
|
+
if key in _cache:
|
|
19
|
+
return _cache[key]
|
|
20
|
+
|
|
21
|
+
path = os.path.join(_LOCALES_DIR, lang, f"{module_name}.json")
|
|
22
|
+
if not os.path.exists(path):
|
|
23
|
+
_cache[key] = {"aliases": {}, "docs": {}}
|
|
24
|
+
return _cache[key]
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# utf-8-sig: 윈도우 메모장/일부 에디터가 붙이는 BOM 이 있어도 읽힌다
|
|
28
|
+
with open(path, "r", encoding="utf-8-sig") as f:
|
|
29
|
+
data = json.load(f)
|
|
30
|
+
except json.JSONDecodeError as e:
|
|
31
|
+
raise ValueError(f"locale 파일 JSON 오류: {path} ({e})") from e
|
|
32
|
+
|
|
33
|
+
result = {
|
|
34
|
+
"aliases": data.get("aliases", {}),
|
|
35
|
+
"docs": data.get("docs", {}),
|
|
36
|
+
}
|
|
37
|
+
_cache[key] = result
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_locales_for_module(module_name, langs):
|
|
42
|
+
return {lang: load_locale(module_name, lang) for lang in langs}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def clear_cache():
|
|
46
|
+
"""JSON 을 고친 뒤 재시작 없이 다시 읽고 싶을 때 (GUI/테스트용)."""
|
|
47
|
+
_cache.clear()
|
|
48
|
+
try:
|
|
49
|
+
from .alias_installer import _alias_table, _make_alias_function
|
|
50
|
+
_alias_table.cache_clear()
|
|
51
|
+
_make_alias_function.cache_clear()
|
|
52
|
+
except ImportError:
|
|
53
|
+
pass
|