pyflowlauncher 0.1.0.dev0__tar.gz → 0.3.0.dev0__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.
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/PKG-INFO +1 -1
- pyflowlauncher-0.3.0.dev0/pyflowlauncher/manifest.py +25 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/plugin.py +14 -0
- pyflowlauncher-0.3.0.dev0/pyflowlauncher/string_matcher.py +222 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/PKG-INFO +1 -1
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/SOURCES.txt +2 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyproject.toml +2 -2
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/README.md +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/__init__.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/api.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/event.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/icons.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/jsonrpc.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/method.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/result.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher/shared.py +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/dependency_links.txt +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/requires.txt +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/top_level.txt +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/setup.cfg +0 -0
- {pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/setup.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import TypedDict, Literal
|
|
2
|
+
|
|
3
|
+
MANIFEST_FILE = 'plugin.json'
|
|
4
|
+
|
|
5
|
+
Languages = Literal[
|
|
6
|
+
'Python',
|
|
7
|
+
'CSharp',
|
|
8
|
+
'FSharp',
|
|
9
|
+
'Executable',
|
|
10
|
+
'TypeScript',
|
|
11
|
+
'JavaScript',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginManifestSchema(TypedDict):
|
|
16
|
+
ID: str
|
|
17
|
+
ActionKeyword: str
|
|
18
|
+
Name: str
|
|
19
|
+
Description: str
|
|
20
|
+
Author: str
|
|
21
|
+
Version: str
|
|
22
|
+
Language: Languages
|
|
23
|
+
Website: str
|
|
24
|
+
IcoPath: str
|
|
25
|
+
ExecuteFileName: str
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import sys
|
|
3
4
|
from functools import wraps
|
|
4
5
|
from typing import Any, Callable, Iterable, Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
5
8
|
|
|
6
9
|
from pyflowlauncher.shared import logger
|
|
7
10
|
|
|
8
11
|
from .event import EventHandler
|
|
9
12
|
from .jsonrpc import JsonRPCClient
|
|
10
13
|
from .result import JsonRPCAction, ResultResponse
|
|
14
|
+
from .manifest import PluginManifestSchema, MANIFEST_FILE
|
|
11
15
|
|
|
12
16
|
Method = Callable[..., Union[ResultResponse, JsonRPCAction]]
|
|
13
17
|
|
|
@@ -54,3 +58,13 @@ class Plugin:
|
|
|
54
58
|
if 'result' in feedback and self._settings is not None:
|
|
55
59
|
feedback['SettingsChange'] = self.settings
|
|
56
60
|
self._client.send(feedback)
|
|
61
|
+
|
|
62
|
+
def plugin_root_dir(self) -> Path:
|
|
63
|
+
"""Return the root directory of the plugin."""
|
|
64
|
+
return Path(sys.argv[0]).parent
|
|
65
|
+
|
|
66
|
+
def manifest(self) -> PluginManifestSchema:
|
|
67
|
+
"""Return the plugin manifest."""
|
|
68
|
+
with open(self.plugin_root_dir() / MANIFEST_FILE, 'r', encoding='utf-8') as f:
|
|
69
|
+
manifest = json.load(f)
|
|
70
|
+
return manifest
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
SPACE_CHAR: str = ' '
|
|
5
|
+
QUERY_SEARCH_PRECISION = {
|
|
6
|
+
'Regular': 50,
|
|
7
|
+
'Low': 20,
|
|
8
|
+
'None': 0
|
|
9
|
+
}
|
|
10
|
+
DEFAULT_QUERY_SEARCH_PRECISION = QUERY_SEARCH_PRECISION['Regular']
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
This is a python copy of Flow Launcher's string matcher.
|
|
14
|
+
I take no credit for the algorithm, I just translated it to python.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class MatchData:
|
|
20
|
+
"""Match data"""
|
|
21
|
+
matched: bool
|
|
22
|
+
score_cutoff: int
|
|
23
|
+
index_list: List[int] = field(default_factory=list)
|
|
24
|
+
score: int = 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def string_matcher(query: str, text: str, ignore_case: bool = True,
|
|
28
|
+
query_search_precision: int = DEFAULT_QUERY_SEARCH_PRECISION) -> MatchData:
|
|
29
|
+
"""Compare query to text"""
|
|
30
|
+
if not text or not query:
|
|
31
|
+
return MatchData(False, query_search_precision)
|
|
32
|
+
|
|
33
|
+
query = query.strip()
|
|
34
|
+
|
|
35
|
+
current_acronym_query_index = 0
|
|
36
|
+
acronym_match_data: List[int] = []
|
|
37
|
+
acronyms_total_count: int = 0
|
|
38
|
+
acronyms_matched: int = 0
|
|
39
|
+
|
|
40
|
+
full_text_lower: str = text.lower() if ignore_case else text
|
|
41
|
+
query_lower: str = query.lower() if ignore_case else query
|
|
42
|
+
|
|
43
|
+
query_substrings: List[str] = query_lower.split(' ')
|
|
44
|
+
current_query_substring_index: int = 0
|
|
45
|
+
current_query_substring = query_substrings[current_query_substring_index]
|
|
46
|
+
current_query_substring_char_index = 0
|
|
47
|
+
|
|
48
|
+
first_match_index = -1
|
|
49
|
+
first_match_index_in_word = -1
|
|
50
|
+
last_match_index = 0
|
|
51
|
+
all_query_substrings_matched: bool = False
|
|
52
|
+
match_found_in_previous_loop: bool = False
|
|
53
|
+
all_substrings_contained_in_text: bool = True
|
|
54
|
+
|
|
55
|
+
index_list: List[int] = []
|
|
56
|
+
space_indices: List[int] = []
|
|
57
|
+
for text_index in range(len(full_text_lower)):
|
|
58
|
+
if (current_acronym_query_index >= len(query_lower) or
|
|
59
|
+
(current_acronym_query_index >= len(query_lower) and all_query_substrings_matched)):
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
if full_text_lower[text_index] == SPACE_CHAR and current_query_substring_char_index == 0:
|
|
63
|
+
space_indices.append(text_index)
|
|
64
|
+
|
|
65
|
+
if is_acronym(text, text_index):
|
|
66
|
+
if full_text_lower[text_index] == query_lower[current_acronym_query_index]:
|
|
67
|
+
acronym_match_data.append(text_index)
|
|
68
|
+
acronyms_matched += 1
|
|
69
|
+
current_acronym_query_index += 1
|
|
70
|
+
|
|
71
|
+
if is_acronym_count(text, text_index):
|
|
72
|
+
acronyms_total_count += 1
|
|
73
|
+
|
|
74
|
+
if all_query_substrings_matched or (full_text_lower[text_index]
|
|
75
|
+
!= current_query_substring[current_query_substring_char_index]):
|
|
76
|
+
match_found_in_previous_loop = False
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
if first_match_index < 0:
|
|
80
|
+
first_match_index = text_index
|
|
81
|
+
|
|
82
|
+
if current_query_substring_char_index == 0:
|
|
83
|
+
match_found_in_previous_loop = True
|
|
84
|
+
first_match_index_in_word = text_index
|
|
85
|
+
elif not match_found_in_previous_loop:
|
|
86
|
+
start_index_to_verify = text_index - current_query_substring_char_index
|
|
87
|
+
|
|
88
|
+
if all_previous_chars_matched(start_index_to_verify, current_query_substring_char_index, full_text_lower,
|
|
89
|
+
current_query_substring):
|
|
90
|
+
match_found_in_previous_loop = True
|
|
91
|
+
first_match_index_in_word = start_index_to_verify if current_query_substring_index == 0 else first_match_index
|
|
92
|
+
|
|
93
|
+
index_list = get_updated_index_list(
|
|
94
|
+
start_index_to_verify, current_query_substring_char_index, first_match_index_in_word, index_list)
|
|
95
|
+
|
|
96
|
+
last_match_index = text_index + 1
|
|
97
|
+
index_list.append(text_index)
|
|
98
|
+
|
|
99
|
+
current_query_substring_char_index += 1
|
|
100
|
+
|
|
101
|
+
if current_query_substring_char_index == len(current_query_substring):
|
|
102
|
+
all_substrings_contained_in_text = match_found_in_previous_loop and all_substrings_contained_in_text
|
|
103
|
+
|
|
104
|
+
current_query_substring_index += 1
|
|
105
|
+
|
|
106
|
+
all_query_substrings_matched = all_query_substrings_matched_func(
|
|
107
|
+
current_query_substring_index, len(query_substrings))
|
|
108
|
+
|
|
109
|
+
if all_query_substrings_matched:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
current_query_substring = query_substrings[current_query_substring_index]
|
|
113
|
+
current_query_substring_char_index = 0
|
|
114
|
+
|
|
115
|
+
if acronyms_matched > 0 and acronyms_matched == len(query):
|
|
116
|
+
acronyms_score: int = int(acronyms_matched * 100 / acronyms_total_count)
|
|
117
|
+
|
|
118
|
+
if acronyms_score >= query_search_precision:
|
|
119
|
+
return MatchData(True, query_search_precision, acronym_match_data, acronyms_score)
|
|
120
|
+
|
|
121
|
+
if all_query_substrings_matched:
|
|
122
|
+
|
|
123
|
+
nearest_space_index = calculate_closest_space_index(
|
|
124
|
+
space_indices, first_match_index)
|
|
125
|
+
|
|
126
|
+
score = calculate_search_score(query, text, first_match_index - nearest_space_index - 1,
|
|
127
|
+
space_indices, last_match_index - first_match_index, all_substrings_contained_in_text)
|
|
128
|
+
|
|
129
|
+
return MatchData(True, query_search_precision, index_list, score)
|
|
130
|
+
|
|
131
|
+
return MatchData(False, query_search_precision)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def calculate_search_score(query: str, text: str, first_index: int, space_indices: List[int],
|
|
135
|
+
match_length: int, all_substrings_contained_in_text: bool):
|
|
136
|
+
score = 100 * (len(query) + 1) / ((1 + first_index) + (match_length + 1))
|
|
137
|
+
|
|
138
|
+
if first_index == 0 and all_substrings_contained_in_text:
|
|
139
|
+
score -= len(space_indices)
|
|
140
|
+
|
|
141
|
+
if (len(text) - len(query)) < 5:
|
|
142
|
+
score += 20
|
|
143
|
+
elif (len(text) - len(query)) < 10:
|
|
144
|
+
score += 10
|
|
145
|
+
|
|
146
|
+
if all_substrings_contained_in_text:
|
|
147
|
+
count: int = len(query.replace(' ', ''))
|
|
148
|
+
threshold: int = 4
|
|
149
|
+
if count <= threshold:
|
|
150
|
+
score += count * 10
|
|
151
|
+
else:
|
|
152
|
+
score += threshold * 10 + (count - threshold) * 5
|
|
153
|
+
|
|
154
|
+
return score
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_updated_index_list(start_index_to_verify: int,
|
|
158
|
+
current_query_substring_char_index: int,
|
|
159
|
+
first_matched_index_in_word: int, index_list: List[int]):
|
|
160
|
+
updated_list: List[int] = []
|
|
161
|
+
|
|
162
|
+
for idx, item in enumerate(index_list):
|
|
163
|
+
if item >= first_matched_index_in_word:
|
|
164
|
+
index_list.pop(idx)
|
|
165
|
+
|
|
166
|
+
updated_list.extend(index_list)
|
|
167
|
+
|
|
168
|
+
for i in range(current_query_substring_char_index):
|
|
169
|
+
updated_list.append(start_index_to_verify + i)
|
|
170
|
+
|
|
171
|
+
return updated_list
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def all_query_substrings_matched_func(current_query_substring_index: int, query_substrings_length: int) -> bool:
|
|
175
|
+
return current_query_substring_index >= query_substrings_length
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def all_previous_chars_matched(start_index_to_verify: int,
|
|
179
|
+
current_query_substring_char_index: int,
|
|
180
|
+
full_text_lower: str, current_query_substring: str) -> bool:
|
|
181
|
+
all_match = True
|
|
182
|
+
for i in range(current_query_substring_char_index):
|
|
183
|
+
if full_text_lower[start_index_to_verify + i] != current_query_substring[i]:
|
|
184
|
+
all_match = False
|
|
185
|
+
|
|
186
|
+
return all_match
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def is_acronym(text: str, text_index: int) -> bool:
|
|
190
|
+
if is_acronym_char(text, text_index) or is_acronym_number(text, text_index):
|
|
191
|
+
return True
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def is_acronym_count(text: str, text_index: int) -> bool:
|
|
196
|
+
if is_acronym_char(text, text_index):
|
|
197
|
+
return True
|
|
198
|
+
if is_acronym_number(text, text_index):
|
|
199
|
+
return text_index == 0 or text[text_index - 1] == SPACE_CHAR
|
|
200
|
+
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def is_acronym_char(text: str, text_index: int) -> bool:
|
|
205
|
+
return text[text_index].isupper() or text_index == 0 or text[text_index - 1] == SPACE_CHAR
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def is_acronym_number(text: str, text_index: int) -> bool:
|
|
209
|
+
return text[text_index].isdigit()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def calculate_closest_space_index(space_indices: List[int], first_match_index: int) -> int:
|
|
213
|
+
|
|
214
|
+
closest_space_index = -1
|
|
215
|
+
|
|
216
|
+
for i in space_indices:
|
|
217
|
+
if i < first_match_index:
|
|
218
|
+
closest_space_index = i
|
|
219
|
+
else:
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
return closest_space_index
|
|
@@ -6,10 +6,12 @@ pyflowlauncher/api.py
|
|
|
6
6
|
pyflowlauncher/event.py
|
|
7
7
|
pyflowlauncher/icons.py
|
|
8
8
|
pyflowlauncher/jsonrpc.py
|
|
9
|
+
pyflowlauncher/manifest.py
|
|
9
10
|
pyflowlauncher/method.py
|
|
10
11
|
pyflowlauncher/plugin.py
|
|
11
12
|
pyflowlauncher/result.py
|
|
12
13
|
pyflowlauncher/shared.py
|
|
14
|
+
pyflowlauncher/string_matcher.py
|
|
13
15
|
pyflowlauncher.egg-info/PKG-INFO
|
|
14
16
|
pyflowlauncher.egg-info/SOURCES.txt
|
|
15
17
|
pyflowlauncher.egg-info/dependency_links.txt
|
|
@@ -6,7 +6,7 @@ name = "pyflowlauncher"
|
|
|
6
6
|
authors = [
|
|
7
7
|
{name = "William McAllister", email = "dev.garulf@gmail.com"}
|
|
8
8
|
]
|
|
9
|
-
version = '0.
|
|
9
|
+
version = '0.3.0-dev.0'
|
|
10
10
|
description = "Python library to help build Flow Launcher plugins."
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.8"
|
|
@@ -24,7 +24,7 @@ dependencies = [
|
|
|
24
24
|
packages = ["pyflowlauncher"]
|
|
25
25
|
|
|
26
26
|
[tool.bumpversion]
|
|
27
|
-
current_version = "0.
|
|
27
|
+
current_version = "0.3.0-dev.0"
|
|
28
28
|
parse = """(?x)
|
|
29
29
|
(?P<major>[0-9]+)
|
|
30
30
|
\\.(?P<minor>[0-9]+)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/requires.txt
RENAMED
|
File without changes
|
{pyflowlauncher-0.1.0.dev0 → pyflowlauncher-0.3.0.dev0}/pyflowlauncher.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|