Skip to content

Commit 4eeb4ee

Browse files
committed
New oopmultilang,LICENSE
1 parent 0f70bbd commit 4eeb4ee

File tree

6 files changed

+309
-2
lines changed

6 files changed

+309
-2
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023-present, leoweyr
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
# Python-OOPMultilang
2-
A Python package that supports multi-language conversion of the object-oriented paradigm.
1+
# OOPMultilang
2+
3+
A Python package that supports multi-language conversion of the object-oriented paradigm.

oopmultilang/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .lang import (
2+
Lang,
3+
load_mode,
4+
LangDictSearchError
5+
)
6+
7+
__all__ = ["Lang", "load_mode", "LangDictSearchError"]

oopmultilang/_errors.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import inspect
2+
import os
3+
import sys
4+
from enum import Enum
5+
6+
7+
class ExpressionError(Exception):
8+
def __init__(self, expression):
9+
self.__m_expression = expression
10+
self.__m_traceback_filename = inspect.getframeinfo(inspect.currentframe().f_back).filename
11+
self.__m_tracebakc_lineno = inspect.currentframe().f_back.f_lineno
12+
self.__m_message = "invalid expression"
13+
# Use Python default exception handler to handle this exception.
14+
sys.last_type = type(self)
15+
sys.last_value = self
16+
sys.last_traceback = self.__traceback__
17+
18+
def __str__(self):
19+
if self.__m_expression.count(
20+
"as") > 1: # If the expression contains more than one "as" keyword, find the error index and indicate it.
21+
error_index = self.__m_expression.find("as", self.__m_expression.find("as") + 1)
22+
error_message = f"""File \"{os.path.abspath(self.__m_traceback_filename)}\", line {self.__m_tracebakc_lineno}\
23+
\n\t{self.__m_expression}\
24+
\n\t{' ' * error_index}^\
25+
\nExpressionError: {self.__m_message}
26+
"""
27+
return error_message
28+
29+
30+
class LangDictSearchError(Exception, Enum):
31+
# Error type.
32+
UNIVERSAL_WORD_EXIST_ERROR = 1
33+
SOURCE_LANG_DICT_MATCH_ERROR = 2
34+
TARGET_LANG_DICT_MATCH_ERROR = 3
35+
36+
def __init__(self, error_type, error_word, source_lang, target_lang):
37+
self.__m_error_type = error_type
38+
self.__m_error_word = error_word
39+
self.__m_error_position = None
40+
self.__m_message = ""
41+
42+
# Different error conditions.
43+
if self.__m_error_type == LangDictSearchError.UNIVERSAL_WORD_EXIST_ERROR:
44+
self.__m_message = f"'{self.__m_error_word}' is not the universal word in language dictionaries"
45+
elif self.__m_error_type == LangDictSearchError.SOURCE_LANG_DICT_MATCH_ERROR:
46+
self.__m_error_position = source_lang
47+
self.__m_message = f"'{self.__m_error_word}' is not the native word in '{self.__m_error_position}' dictionary"
48+
elif self.__m_error_type == LangDictSearchError.TARGET_LANG_DICT_MATCH_ERROR:
49+
self.__m_error_position = target_lang
50+
self.__m_message = f"'{self.__m_error_word}' can not be converted to the native word in '{self.__m_error_position}' dictionary"
51+
52+
def __str__(self):
53+
return self.__m_message
54+
55+
@property
56+
def type(self):
57+
return self.__m_error_type
58+
59+
@property
60+
def word(self):
61+
return self.word
62+
63+
@property
64+
def position(self):
65+
return self.__m_error_position

oopmultilang/_lang_dict_parser.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class LangDictParser:
2+
"""
3+
Each lang dict data structure is uniformly composed of universal word and native word in pairs.
4+
5+
- ``universal word``: Similar to the object-oriented usage, generally expressed in English.
6+
- ``native word``: Native word of the language associated with the lang dict.
7+
"""
8+
@classmethod
9+
def loads(cls, lang_dict):
10+
pass
11+
12+
@classmethod
13+
def dumps(cls, python_dict: dict):
14+
pass
15+
16+
17+
class LangTypeLangDict(LangDictParser):
18+
@classmethod
19+
def loads(cls, lang_dict: str):
20+
python_dict = {}
21+
line_index = 1
22+
for line in lang_dict.split('\n'):
23+
line = line.strip()
24+
if not line or line.startswith("#"):
25+
continue
26+
if "#" in line:
27+
line = line[:line.index("#")]
28+
if "=" not in line:
29+
raise ValueError(f"invalid lang type data, line {line_index} is missing at most one equals sign")
30+
key, value = line.split("=")
31+
python_dict[key.strip()] = value.strip()
32+
line_index += 1
33+
return python_dict
34+
35+
@classmethod
36+
def dumps(cls, python_dict: dict):
37+
if not isinstance(python_dict, dict):
38+
raise ValueError("invalid dict")
39+
lang_dict = ""
40+
for key, value in python_dict.items():
41+
lang_dict += f"{key} = {value}\n"
42+
return lang_dict

oopmultilang/lang.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
from enum import Enum
2+
3+
"""
4+
An enumeration representing two loading modes.
5+
6+
- ``REALTIME``: Static data files are read for each conversion.
7+
- ``ONETIME``: Static data files are only read once when the object is instantiated for the first time.
8+
"""
9+
load_mode = Enum('LoadMode', ('REALTIME', 'ONETIME'))
10+
11+
12+
from _errors import ExpressionError
13+
from _errors import LangDictSearchError
14+
from _lang_dict_parser import LangTypeLangDict
15+
from easierfile import File
16+
17+
18+
class _LangUniversal():
19+
def __init__(self, super_self, lang_dicts):
20+
self.__m_dict = lang_dicts
21+
self.__m_super_self = super_self
22+
23+
def __getattr__(self, universal_word_form_call):
24+
"""
25+
Chained calls enable the universal word to support object-oriented expression without string quotes in the code.
26+
"""
27+
universal_word_form_call_chain = [universal_word_form_call]
28+
super_self = self.__m_super_self
29+
lang_dicts = self.__m_dict
30+
31+
class ChainedClass:
32+
def __getattr__(self, universal_word_form_call):
33+
universal_word_form_call_chain.append(universal_word_form_call)
34+
return ChainedClass()
35+
36+
def __repr__(self):
37+
"""
38+
Final settlement of Chained Calls.
39+
"""
40+
if universal_word_form_call_chain[-1] in lang_dicts.keys():
41+
return super_self.str('.'.join(universal_word_form_call_chain[0:-1]), None, universal_word_form_call_chain[-1])
42+
else:
43+
return super_self.str('.'.join(universal_word_form_call_chain))
44+
45+
return ChainedClass()
46+
47+
48+
class Lang:
49+
def __init__(self, include=(), load_mode=load_mode.REALTIME):
50+
self.__m_dict = {}
51+
self._m_load_mode = load_mode
52+
53+
# None represents the universal word.
54+
self.__m_default_source_lang = None
55+
self.__m_default_target_lang = None
56+
57+
if len(include) != 0:
58+
for expression in include:
59+
"""
60+
Parses expressions imported into language dictionaries via param<include>.
61+
62+
There are two forms of this expression, one is full and the other is short.
63+
64+
For example:
65+
66+
1. ``full``: './en_us.lang as en_us'
67+
2. ``short``: './en_us.lang'
68+
69+
Note that when using the short form, the name of the associated language will be the name of the file.
70+
"""
71+
if expression.find("as") != -1:
72+
if expression.count("as") == 1:
73+
"""
74+
A list of strings representing the distinct terms of the expression after it is parsed.
75+
76+
- ``expression_parsing[0]``: The file path of the language dictionary.
77+
- ``expression_parsing[1]``: The associated language of the language dictionary.
78+
"""
79+
expression_parsing = [expression_unit.strip() for expression_unit in expression]
80+
dictionary_file = File(expression_parsing[0])
81+
dictionary_associated_lang = expression_parsing[1]
82+
else:
83+
raise ExpressionError(expression)
84+
else:
85+
dictionary_file = File(expression)
86+
dictionary_associated_lang = dictionary_file.info["name"]
87+
88+
# Store language dictionary file information for different languages into member:private<self.__m_dict>.
89+
if not dictionary_file.status["exist"]:
90+
raise FileNotFoundError("Language dictionary not found: " + dictionary_file.info["path"])
91+
else:
92+
if dictionary_associated_lang in self.__m_dict.keys():
93+
self.__m_dict[dictionary_associated_lang].append(dictionary_file.info["path"])
94+
else:
95+
self.__m_dict[dictionary_associated_lang] = [dictionary_file.info["path"]]
96+
97+
# One-time loading mode.
98+
if self._m_load_mode == load_mode.ONETIME:
99+
self.__load_lang_dict()
100+
101+
def __load_lang_dict(self):
102+
new_dict = self.__m_dict.copy()
103+
for dictionary_associated_lang, dictionary in self.__m_dict.items():
104+
if isinstance(dictionary, list): # Indicates that dictionary is a list of lang dict file path.
105+
new_dict[dictionary_associated_lang] = {}
106+
for dictionary_file_path in dictionary:
107+
dict_file = File(dictionary_file_path)
108+
if dict_file.info["ext"] == "lang": # Parse the lang dict of type .lang
109+
new_dict[dictionary_associated_lang].update(LangTypeLangDict.loads(dict_file.content))
110+
self.__m_dict = new_dict
111+
112+
def default_lang(self, source_lang=None, target_lang=None):
113+
if source_lang is not None:
114+
self.__m_default_source_lang = source_lang
115+
if target_lang is not None:
116+
self.__m_default_target_lang = target_lang
117+
118+
def str(self, word, source_lang=None, target_lang=None):
119+
# Whether are default langs setting.
120+
if source_lang is None:
121+
source_lang = self.__m_default_source_lang
122+
if target_lang is None:
123+
target_lang = self.__m_default_target_lang
124+
125+
# Real-time loading mode.
126+
if self._m_load_mode == load_mode.REALTIME:
127+
self.__load_lang_dict()
128+
129+
# Initialize lang dict search transition value.
130+
source_word = word
131+
universal_word = None
132+
target_word = None
133+
134+
# Search for the universal word corresponding to the native word.
135+
if source_lang is None:
136+
is_matched = False
137+
for dictionary in self.__m_dict.values():
138+
if source_word in dictionary.keys():
139+
universal_word = source_word
140+
is_matched = True
141+
if not is_matched:
142+
raise LangDictSearchError(
143+
LangDictSearchError.UNIVERSAL_WORD_EXIST_ERROR, source_word, source_lang, target_lang)
144+
else:
145+
is_matched = False
146+
for dictionary_universal_word, dictionary_native_word in self.__m_dict[source_lang].items():
147+
if dictionary_native_word == source_word:
148+
universal_word = dictionary_universal_word
149+
is_matched = True
150+
if not is_matched:
151+
raise LangDictSearchError(
152+
LangDictSearchError.SOURCE_LANG_DICT_MATCH_ERROR, source_word, source_lang, target_lang)
153+
154+
# Search for the native word corresponding to the universal word.
155+
if target_lang is None:
156+
target_word = universal_word
157+
else:
158+
is_matched = False
159+
for dictionary_universal_word, dictionary_native_word in self.__m_dict[target_lang].items():
160+
if dictionary_universal_word == universal_word:
161+
target_word = dictionary_native_word
162+
is_matched =True
163+
if not is_matched:
164+
raise LangDictSearchError(
165+
LangDictSearchError.TARGET_LANG_DICT_MATCH_ERROR, source_word, source_lang, target_lang)
166+
167+
return target_word
168+
169+
@property
170+
def universal(self):
171+
return _LangUniversal(self, self.__m_dict)

0 commit comments

Comments
 (0)