Skip to content

Commit 3ac1913

Browse files
committed
Complex -f in list, and additions
This commit adds complex filters to the ``-f|--filter`` parameter of the list action in bottles-cli. The expanded functionality allows you to filter by multiple fields and values, and to use glob patterns to match. Additionally, added the missing ability to ``list dependencies``, and migrated the programs action over to the list action, where the complex filter ``bottle:`` can be used to target listing programs only found in a single bottle (or multiple with glob match)
1 parent 658dde6 commit 3ac1913

File tree

5 files changed

+282
-46
lines changed

5 files changed

+282
-46
lines changed

Diff for: bottles/backend/managers/manager.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def __init__(
118118
self.is_cli = is_cli
119119
self.settings = g_settings or GSettingsStub
120120
self.utils_conn = ConnectionUtils(
121-
force_offline=self.is_cli or self.settings.get_boolean("force-offline")
121+
force_offline=self.settings.get_boolean("force-offline")
122122
)
123123
self.data_mgr = DataManager()
124124
_offline = True
@@ -141,7 +141,7 @@ def __init__(
141141
if self.repository_manager.aborted_connections > 0:
142142
self.utils_conn.status = False
143143
_offline = True
144-
144+
145145
times["RepositoryManager"] = time.time()
146146
self.versioning_manager = VersioningManager(self)
147147
times["VersioningManager"] = time.time()
@@ -153,10 +153,10 @@ def __init__(
153153
self.steam_manager = SteamManager()
154154
times["SteamManager"] = time.time()
155155

156-
if not self.is_cli:
157-
times.update(self.checks(install_latest=False, first_run=True).data)
158-
else:
156+
if self.is_cli is True:
159157
logging.set_silent()
158+
159+
times.update(self.checks(install_latest=False, first_run=True).data)
160160

161161
if "BOOT_TIME" in os.environ:
162162
_temp_times = times.copy()
@@ -956,6 +956,8 @@ def process_bottle(bottle):
956956
):
957957
self.steam_manager.update_bottles()
958958
self.local_bottles.update(self.steam_manager.list_prefixes())
959+
960+
EventManager.done(Events.BottlesFetching)
959961

960962
# Update parameters in bottle config
961963
def update_config(

Diff for: bottles/backend/state.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ class Events(Enum):
1919
ComponentsFetching = "components.fetching"
2020
DependenciesFetching = "dependencies.fetching"
2121
InstallersFetching = "installers.fetching"
22+
BottlesFetching = "bottles.fetching"
2223
ComponentsOrganizing = "components.organizing"
2324
DependenciesOrganizing = "dependencies.organizing"
2425
InstallersOrganizing = "installers.organizing"
26+
BottlesOrganizing = "bottles.organizing"
2527

2628

2729
class Signals(Enum):

Diff for: bottles/backend/utils/generic.py

+66
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def is_glibc_min_available():
9191

9292

9393
def sort_by_version(_list: list, extra_check: str = "async"):
94+
"""Sort a list of strings by version."""
9495
def natural_keys(text):
9596
result = [int(re.search(extra_check, text) is None)]
9697
result.extend(
@@ -112,6 +113,71 @@ def get_mime(path: str):
112113

113114

114115
def random_string(length: int):
116+
"""Generate a random string of given length."""
115117
return "".join(
116118
random.choice(string.ascii_uppercase + string.digits) for _ in range(length)
117119
)
120+
121+
def glob_to_re(pat: str) -> re.Pattern[str]:
122+
"""Convert a glob (UNIX-like) match pattern to a regular expression."""
123+
i, n = 0, len(pat)
124+
res = ''
125+
while i < n:
126+
c = pat[i]
127+
i = i+1
128+
if c == '*':
129+
j = i
130+
if j < n and pat[j] == '*':
131+
res = res + '.*'
132+
i = j+1
133+
else:
134+
res = res + '[^/]*'
135+
elif c == '?':
136+
res = res + '[^/]'
137+
elif c == '[':
138+
j = i
139+
if j < n and pat[j] == '!':
140+
j = j+1
141+
if j < n and pat[j] == ']':
142+
j = j+1
143+
while j < n and pat[j] != ']':
144+
j = j+1
145+
if j >= n:
146+
res = res + '\\['
147+
else:
148+
stuff = pat[i:j]
149+
if '--' not in stuff:
150+
stuff = stuff.replace('\\', r'\\')
151+
else:
152+
chunks = []
153+
k = i+2 if pat[i] == '!' else i+1
154+
while True:
155+
k = pat.find('-', k, j)
156+
if k < 0:
157+
break
158+
chunks.append(pat[i:k])
159+
i = k+1
160+
k = k+3
161+
chunks.append(pat[i:j])
162+
stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-')
163+
for s in chunks)
164+
stuff = re.sub(r'([&~|])', r'\\\1', stuff)
165+
i = j+1
166+
if stuff[0] == '!':
167+
stuff = '^/' + stuff[1:]
168+
elif stuff[0] in ('^', '['):
169+
stuff = '\\' + stuff
170+
res = '%s[%s]' % (res, stuff)
171+
else:
172+
res = res + re.escape(c)
173+
return re.compile(r'(?s:%s)\Z' % res)
174+
175+
176+
def glob_filter(objects: list | dict | str, pattern: str) -> list | dict | str | None:
177+
"""Filter objects by a glob (UNIX-like) pattern."""
178+
if isinstance(objects, dict):
179+
return {k: v for k, v in objects.items() if re.match(glob_to_re(pattern), k)}
180+
elif isinstance(objects, list):
181+
return [i for i in objects if re.match(glob_to_re(pattern), i)]
182+
elif isinstance(objects, str):
183+
return objects if re.match(glob_to_re(pattern), objects) else None

0 commit comments

Comments
 (0)