From: Urban Wallasch Date: Thu, 17 Jun 2021 18:23:33 +0000 (+0200) Subject: * Implemented de-inflected verb search option (experimental). X-Git-Tag: v0.1.0~117 X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=d9760d511c3ea66d0a4fd83e23992d36cf0e2964;p=jiten-pai.git * Implemented de-inflected verb search option (experimental). * Improved heuristic to locate configuration and support files. --- diff --git a/ROADMAP.txt b/ROADMAP.txt index a8a705d..4cd69fb 100644 --- a/ROADMAP.txt +++ b/ROADMAP.txt @@ -16,7 +16,7 @@ Phase II [x] Romaji input [ ] app icon, "About" dialog [ ] README -[ ] verb de-inflection? +[x] verb de-inflection Phase III diff --git a/jiten-pai.py b/jiten-pai.py index 56de2b9..bbcf27d 100755 --- a/jiten-pai.py +++ b/jiten-pai.py @@ -13,9 +13,11 @@ See `LICENSE` file for more information. """ -_JITENPAI_VERSION = '0.0.6' +_JITENPAI_VERSION = '0.0.7' _JITENPAI_NAME = 'Jiten-pai' +_JITENPAI_DIR = 'jiten-pai' _JITENPAI_CFG = 'jiten-pai.conf' +_JITENPAI_VCONJ = 'vconj.utf8' _JITENPAI_HELP = 'todo' @@ -80,22 +82,14 @@ cfg = { 'lfont': 'IPAPMincho', 'lfont_sz': 24.0, 'hl_col': 'blue', + 'deinflect': False, 'max_hist': 12, 'history': [], # run-time only, not saved: 'cfgfile': None, } -def _save_cfg(): - s_cfg = cfg.copy() - s_cfg.pop('cfgfile', None) - if cfg['cfgfile']: - try: - with open(cfg['cfgfile'], 'w') as cfgfile: - json.dump(s_cfg, cfgfile, indent=2) - return - except Exception as e: - eprint(cfg['cfgfile'], str(e)) +def _get_cfile_path(fname, mode=os.R_OK): cdirs = [] if os.environ.get('APPDATA'): cdirs.append(os.environ.get('APPDATA')) @@ -103,37 +97,98 @@ def _save_cfg(): cdirs.append(os.environ.get('XDG_CONFIG_HOME')) if os.environ.get('HOME'): cdirs.append(os.path.join(os.environ.get('HOME'), '.config')) - cdirs.append(os.environ.get('HOME')) cdirs.append(os.path.dirname(os.path.realpath(__file__))) for d in cdirs: - cf = os.path.join(d, _JITENPAI_CFG) + path = os.path.join(d, fname) + if os.access(path, mode): + return path + return fname + +def _save_cfg(): + s_cfg = cfg.copy() + s_cfg.pop('cfgfile', None) + if cfg['cfgfile']: try: - with open(cf, 'w') as cfgfile: + with open(cfg['cfgfile'], 'w') as cfgfile: json.dump(s_cfg, cfgfile, indent=2) return except Exception as e: - eprint(cf, str(e)) + eprint(cfg['cfgfile'], str(e)) + cfgdir = _get_cfile_path('', mode=os.R_OK | os.W_OK | os.X_OK) + cfname = os.path.join(cfgdir, _JITENPAI_CFG) + try: + with open(cfname, 'w') as cfgfile: + json.dump(s_cfg, cfgfile, indent=2) + cfg['cfgfile'] = cfname + return + except Exception as e: + eprint(cfname, str(e)) def _load_cfg(): + cfname = _get_cfile_path('', mode=os.R_OK) + cfname = os.path.join(cfname, _JITENPAI_CFG) + try: + with open(cfname, 'r') as cfgfile: + cfg.update(json.load(cfgfile)) + cfg['cfgfile'] = cfname + return + except Exception as e: + eprint(cfname, str(e)) + pass + + +############################################################ +# verb de-inflection + +_vc_type = dict() +_vc_deinf = [] + +def _get_dfile_path(fname, mode=os.R_OK): cdirs = [] if os.environ.get('APPDATA'): cdirs.append(os.environ.get('APPDATA')) - if os.environ.get('XDG_CONFIG_HOME'): - cdirs.append(os.environ.get('XDG_CONFIG_HOME')) if os.environ.get('HOME'): - cdirs.append(os.path.join(os.environ.get('HOME'), '.config')) - cdirs.append(os.environ.get('HOME')) + cdirs.append(os.path.join(os.environ.get('HOME'), '.local/share')) + cdirs.append('/usr/local/share') + cdirs.append('/usr/share') cdirs.append(os.path.dirname(os.path.realpath(__file__))) for d in cdirs: - cf = os.path.join(d, _JITENPAI_CFG) - try: - with open(cf, 'r') as cfgfile: - cfg.update(json.load(cfgfile)) - cfg['cfgfile'] = cf - return - except Exception as e: - eprint(cf, str(e)) - pass + path = os.path.join(d, fname) + if os.access(path, mode): + return path + return fname + +def _vc_load(): + vcname = _JITENPAI_VCONJ + if not os.access(vcname, os.R_OK): + vcname = _get_dfile_path(os.path.join(_JITENPAI_DIR, _JITENPAI_VCONJ), mode=os.R_OK) + try: + with open(vcname) as vcfile: + re_type = re.compile(r'^(\d+)\s+(.+)$') + re_deinf = re.compile(r'^\s*([^#\s]+)\s+(\S+)\s+(\d+)\s*$') + for line in vcfile: + match = re_type.match(line) + if match: + _vc_type[match.group(1)] = match.group(2) + continue + match = re_deinf.match(line) + if match: + r = re.compile('%s$' % match.group(1)) + _vc_deinf.append([r, match.group(1), match.group(2), match.group(3)]) + continue + except Exception as e: + eprint(vcname, str(e)) + pass + +def _vc_deinflect(verb): + inf = verb + blurb = '' + for p in _vc_deinf: + v = p[0].sub(p[2], verb) + if v != verb: + inf = v + blurb = '%s %s → %s' % (_vc_type[p[3]], p[1], p[2]) + return inf, blurb ############################################################ @@ -606,7 +661,14 @@ class prefDialog(QDialog): fonts_layout.addRow(self.lfont_button, self.lfont_edit) fonts_layout.addRow(self.color_button, self.color_edit) fonts_layout.addRow('Sample', self.font_sample) - fonts_group.setLayout(fonts_layout) + # search options + search_group = zQGroupBox('Search Options') + self.search_deinflect = QCheckBox('&Verb Deinflection (experimental)') + self.search_deinflect.setChecked(cfg['deinflect']) + self.search_deinflect.setEnabled(len(_vc_deinf) > 0) + search_layout = zQVBoxLayout(search_group) + search_layout.addWidget(self.search_deinflect) + search_layout.addSpacing(10) # dicts dicts_group = zQGroupBox('Dictionaries') self.dict_list = QTreeWidget() @@ -667,6 +729,7 @@ class prefDialog(QDialog): # dialog layout main_layout = QVBoxLayout(self) main_layout.addWidget(fonts_group) + main_layout.addWidget(search_group) main_layout.addWidget(dicts_group) main_layout.addStretch() main_layout.addLayout(button_layout) @@ -735,6 +798,7 @@ class prefDialog(QDialog): cfg['hl_col'] = color.name() self.color_edit.setText(color.name()) self.update_font_sample() + cfg['deinflect'] = self.search_deinflect.isChecked() d = [] it = QTreeWidgetItemIterator(self.dict_list) while it.value(): @@ -1066,6 +1130,37 @@ class jpMainWindow(QMainWindow): return True return False + def _search_deinflected(self, term, mode, limit): + inf, blurb = _vc_deinflect(term) + if not blurb: + return [], inf, '' + result = [] + while 0 == len(result): + # apply search options + s_term = self._search_apply_options(inf, mode) + if s_term[-5:] != '(;|$)': + s_term += '(;|$)' + # perform lookup + if self.genopt_dict.isChecked(): + dic = self.genopt_dictsel.itemData(self.genopt_dictsel.currentIndex()) + result = dict_lookup(dic, s_term, mode, limit) + self.result_group.setTitle(self.result_group.title() + '.') + QApplication.processEvents() + else: + for d in cfg['dicts']: + r = dict_lookup(d[1], s_term, mode, limit) + result.extend(r) + limit -= len(r) + if limit == 0: + limit = -1 + self.result_group.setTitle(self.result_group.title() + '.') + QApplication.processEvents() + # relax search options + if len(result) == 0 and self.genopt_auto.isChecked(): + if not self._search_relax(mode): + break; + return result, inf, blurb + def search(self): self.search_box.setFocus() # validate input @@ -1093,6 +1188,16 @@ class jpMainWindow(QMainWindow): self.result_group.setTitle('Search results: ...') QApplication.processEvents() mode = ScanMode.JAP if contains_cjk(term) else ScanMode.ENG + # search deinflected verb + v_result = [] + v_inf = '' + v_blurb = '' + if cfg['deinflect'] and mode == ScanMode.JAP: + v_result, v_inf, v_blurb = self._search_deinflected(term, mode, limit) + for r in v_result: + r.append(1) + limit -= len(v_result) + # normal search result = [] while 0 == len(result): # apply search options @@ -1117,6 +1222,7 @@ class jpMainWindow(QMainWindow): if not self._search_relax(mode): break; # report results + result.extend(v_result) rlen = len(result) self.result_group.setTitle('Search results: %d%s' % (rlen, '+' if rlen>=limit else '')) QApplication.processEvents() @@ -1124,6 +1230,9 @@ class jpMainWindow(QMainWindow): if rlen > cfg['hardlimit'] / 2: self.result_pane.setPlainText('Formatting...') QApplication.processEvents() + if v_blurb: + verb_message = 'Possible inflected verb or adjective: %s:
' % v_blurb + re_inf = re.compile(v_inf, re.IGNORECASE) re_term = re.compile(term, re.IGNORECASE) re_entity = re.compile(r'EntL\d+X?; *$', re.IGNORECASE) re_mark = re.compile(r'(\(.+?\))') @@ -1145,12 +1254,20 @@ class jpMainWindow(QMainWindow): res[2] = re_entity.sub('', res[2]) # highlight matches if mode == ScanMode.JAP: - res[0] = re_term.sub(lambda m: hl_repl(m, res[0]), kata2hira(res[0])) - res[1] = re_term.sub(hl_repl, res[1]) + if len(res) > 3: + res[0] = re_inf.sub(lambda m: hl_repl(m, res[0]), kata2hira(res[0])) + res[1] = re_inf.sub(hl_repl, res[1]) + else: + res[0] = re_term.sub(lambda m: hl_repl(m, res[0]), kata2hira(res[0])) + res[1] = re_term.sub(hl_repl, res[1]) else: res[2] = re_term.sub(hl_repl, res[2]) # construct display line - html[idx+1] = '

%s%s%s %s

\n' % (lfmt, res[0], (' (%s)'%res[1] if len(res[1]) > 0 else ''), res[2]) + html[idx+1] = '

%s%s%s%s %s

\n' \ + % ((verb_message if len(res)>3 else ''), \ + lfmt, res[0],\ + (' (%s)'%res[1] if len(res[1]) > 0 else ''),\ + res[2]) html[rlen + 1] = '' self.result_pane.setHtml(''.join(html)) self.result_pane.setEnabled(True) @@ -1199,6 +1316,7 @@ def dict_lookup(dict_fname, pattern, mode, limit=0): def main(): _load_cfg() + _vc_load() # set up window os.environ['QT_LOGGING_RULES'] = 'qt5ct.debug=false' app = QApplication(sys.argv) diff --git a/vconj.utf8 b/vconj.utf8 new file mode 100644 index 0000000..fc3bbc5 --- /dev/null +++ b/vconj.utf8 @@ -0,0 +1,358 @@ +# +# V C O N J - control file for verb and adjective deinflection +# +# the following section sets up the labels which are used for the +# various inflections. These are displayed by the program. +# The initial labels can be edited by the user. +# +# First there are the labels for the types of conjugations +# +0 plain, negative, nonpast +1 polite, non-past +2 conditional +3 volitional +4 te-form +5 plain, past +6 plain, negative, past +7 passive +8 causative +9 potential or imperative +10 imperative +11 polite, past +12 polite, negative, non-past +13 polite, negative, past +14 polite, volitional +15 adj. -> adverb +16 adj., past +17 polite +18 polite, volitional +19 passive or potential +20 passive (or potential if Grp 2) +21 adj., negative +22 adj., negative, past +23 adj., past +24 plain verb +25 polite, te-form +# +# and these are the conjugations/inflections, and their dictionary forms +# (please note that these are scanned from the top, so the order is +# critical if the correct guess is to be made.) +# +$ this line flags the start of them +# +た る 5 +て る 4 +かない く 0 +かなか く 6 +きます く 1 +きました く 11 +きまして く 25 +# NB: the order of the two following must not change, as the scan is downwards +きませんでした く 13 +きません く 12 +きましょう く 18 +けば く 2 +こう く 3 +いて く 4 +って く 4 +いた く 5 +った く 5 +かれ く 7 +かせ く 8 +け く 9 +さない す 0 +さなか す 6 +します す 1 +しました す 11 +しまして す 25 +しませんでした す 13 +しません す 12 +しましょう す 18 +せば す 2 +そう す 3 +して す 4 +した す 5 +され す 7 +させ す 8 +せ す 9 +たない つ 0 +たなか つ 6 +ちます つ 1 +ちました つ 11 +ちまして つ 25 +ちませんでした つ 13 +ちません つ 12 +ちましょう つ 18 +てば つ 2 +とう つ 3 +って つ 4 +った つ 5 +たれ つ 7 +たせ つ 8 +て つ 9 +なない ぬ 0 +ななか ぬ 6 +にます ぬ 1 +にました ぬ 11 +にまして ぬ 25 +にませんでした ぬ 13 +にません ぬ 12 +にましょう に 18 +ねば ぬ 2 +のう ぬ 3 +んで ぬ 4 +んだ ぬ 5 +なれ ぬ 7 +なせ ぬ 8 +ね ぬ 9 +まない む 0 +まなか む 6 +みます む 1 +みました む 11 +みまして む 25 +みませんでした む 13 +みません む 12 +みましょう む 18 +めば む 2 +もう む 3 +んで む 4 +んだ む 5 +まれ む 7 +ませ む 8 +め む 9 +らない る 0 +らなか る 6 +ります る 1 +りました る 11 +りまして る 25 +りませんでした る 13 +りません る 12 +りましょう る 18 +れば る 2 +ろう る 3 +って る 4 +った る 5 +られ る 20 +らせ る 8 +# れ る 9 moved below +わない う 0 +わなか う 6 +います う 1 +いました う 11 +いまして う 25 +いませんでした う 13 +いません う 12 +いましょう う 18 +えば う 2 +おう う 3 +って う 4 +った う 5 +われ う 7 +わせ う 8 +え う 9 +がない ぐ 0 +がなか ぐ 6 +ぎます ぐ 1 +ぎました ぐ 11 +ぎまして ぐ 25 +ぎませんでした ぐ 13 +ぎません ぐ 12 +ぎましょう ぐ 18 +げば ぐ 2 +ごう ぐ 3 +いで ぐ 4 +いだ ぐ 5 +がれ ぐ 7 +がせ ぐ 8 +げ ぐ 9 +ばない ぶ 0 +ばなか ぶ 6 +びます ぶ 1 +びました ぶ 11 +びまして ぶ 25 +びませんでした ぶ 13 +びません ぶ 12 +びましょう ぶ 18 +べば ぶ 2 +ぼう ぶ 3 +んで ぶ 4 +んだ ぶ 5 +ばれ ぶ 7 +ばせ ぶ 8 +べ ぶ 9 +ない る 0 +なか る 6 +ます る 1 +ました る 11 +ませんでした る 13 +ません る 12 +ましょう る 18 +れば る 2 +よう る 3 +て る 4 +た る 5 +られ る 20 +させ る 8 +ろ る 10 +らま る 17 +くなか い 22 +くな い 21 +かった い 23 +く い 15 +しか しい 16 +けます ける 1 +けました ける 11 +けませんでした ける 13 +けません ける 12 +けましょう ける 18 +けない ける 0 +けなか ける 6 +けれ ける 2 +けよ ける 3 +けて ける 4 +けた ける 5 +けら ける 19 +けさ ける 8 +けろ ける 10 +げます げる 1 +げました げる 11 +げませんでした げる 13 +げません げる 12 +げましょう げる 18 +げない げる 0 +げなか げる 6 +げて げる 4 +げれ げる 2 +げよ げる 3 +げた げる 5 +げら げる 19 +げさ げる 8 +げろ げる 10 +べます べる 1 +べました べる 11 +べませんでした べる 13 +べません べる 12 +べましょう べる 18 +べない べる 0 +べなか べる 6 +べれ べる 2 +べよ べる 3 +べて べる 4 +べた べる 5 +べら べる 19 +べさ べる 8 +べろ べる 10 +めます める 1 +めました める 11 +めませんでした める 13 +めません める 12 +めましょう める 18 +めない める 0 +めなか める 6 +めれ める 2 +めよ める 3 +めて める 4 +めた める 5 +めら める 19 +めさ める 8 +めろ める 10 +えます える 1 +えました える 11 +えませんでした える 13 +えません える 12 +えましょう える 18 +えない える 0 +えなか える 6 +えれ える 2 +えよ える 3 +えて える 4 +えた える 5 +えら える 19 +えさ える 8 +えろ える 10 +れます れる 1 +れました れる 11 +れませんでした れる 13 +れません れる 12 +れましょう れる 18 +れない れる 0 +れなか れる 6 +れれ れる 2 +れよ れる 3 +れて れる 4 +れた れる 5 +れら れる 19 +れさ れる 8 +れろ れる 10 +れ る 9 +ねます ねる 1 +ねました ねる 11 +ねませんでした ねる 13 +ねません ねる 12 +ねましょう ねる 18 +ねない ねる 0 +ねなか ねる 6 +ねれ ねる 2 +ねよ ねる 3 +ねて ねる 4 +ねた ねる 5 +ねら ねる 19 +ねさ ねる 8 +ねろ ねる 10 +せます せる 1 +せました せる 11 +せませんでした せる 13 +せません せる 12 +せましょう せる 18 +せない せる 0 +せなか せる 6 +せれ せる 2 +せよ せる 3 +せて せる 4 +せた せる 5 +せら せる 19 +せさ せる 8 +せろ せる 10 +ぜます ぜる 1 +ぜました ぜる 11 +ぜませんでした ぜる 13 +ぜません ぜる 12 +ぜましょう ぜる 18 +ぜない ぜる 0 +ぜなか ぜる 6 +ぜれ ぜる 2 +ぜよ ぜる 3 +ぜて ぜる 4 +ぜた ぜる 5 +ぜら ぜる 19 +ぜさ ぜる 8 +ぜろ ぜる 10 +てます てる 1 +てました てる 11 +てませんでした てる 13 +てません てる 12 +てましょう てる 18 +てない てる 0 +てなか てる 6 +てれ てる 2 +てよ てる 3 +てて てる 4 +てた てる 5 +てら てる 19 +てさ てる 8 +てろ てる 10 +でます でる 1 +でました でる 11 +でませんでした でる 13 +でません でる 12 +でましょう でる 18 +でない でる 0 +でなか でる 6 +でれ でる 2 +でよ でる 3 +でて でる 4 +でた でる 5 +でら でる 19 +でさ でる 8 +でろ でる 10 +#く く 24