From 85e0bd8bd5b373896820f99ca9bd6edd0fb8d11e Mon Sep 17 00:00:00 2001 From: Metin Yalcinkaya Date: Sat, 11 Jan 2025 14:36:01 +1100 Subject: [PATCH 1/4] fix: Cache directory import fixed Fixes #5 --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 21d5edd..aa78186 100644 --- a/main.py +++ b/main.py @@ -19,7 +19,7 @@ from ulauncher.api.shared.action.SetUserQueryAction import SetUserQueryAction from ulauncher.api.shared.action.RunScriptAction import RunScriptAction from ulauncher.api.shared.action.ExtensionCustomAction import ExtensionCustomAction -from ulauncher.config import CACHE_DIR +from ulauncher.utils.migrate import CACHE_PATH from devdocs.devdocs_service import DevDocsService gi.require_version('Notify', '0.7') From d42e4d0e57cae23b7f1275d4be2e3f1654fb3343 Mon Sep 17 00:00:00 2001 From: Metin Yalcinkaya Date: Sat, 11 Jan 2025 14:42:56 +1100 Subject: [PATCH 2/4] fix: Cache directory import fixed Fixes #5 Ulauncher API no longer supports CACHE_DIR from ulauncher.config, using CACHE_PATH from ulauncher.utils.migrate --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index aa78186..ff7eff1 100644 --- a/main.py +++ b/main.py @@ -47,7 +47,7 @@ def __init__(self): # initialize DevDocs service. self.devdocs_svc = DevDocsService(LOGGING, - os.path.join(CACHE_DIR, 'devdocs')) + os.path.join(CACHE_PATH, 'devdocs')) def index_docs(self): """ Creates a local index of all the DevDocs resources """ From 29bc4cb22a57d95edfcedcce0fb94e3d0ad82a38 Mon Sep 17 00:00:00 2001 From: Metin Yalcinkaya Date: Sat, 11 Jan 2025 15:56:45 +1100 Subject: [PATCH 3/4] fix: Parses docs prefs as JSON, python version fallback Fixes #7 - Parses docs as JSON before it's passed to set_docs_to_fetch() - Fixes the issue of random languages getting indexed - E.G.: If you put "markdown" and it parses it as a string, it also indexes D and R docs - Devdocs uses versions (python~3.13) for python, so added fallback for when a user enters just "python" --- devdocs/devdocs_service.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/devdocs/devdocs_service.py b/devdocs/devdocs_service.py index af36275..87f24a9 100644 --- a/devdocs/devdocs_service.py +++ b/devdocs/devdocs_service.py @@ -3,6 +3,7 @@ import os import time import difflib +import re import requests @@ -54,6 +55,13 @@ def ensure_cache_dirs(self): def set_docs_to_fetch(self, docs): """ Sets the list of docs that we want to fetch """ + if isinstance(docs, str): + try: + docs = json.loads(docs) + except: + docs = [] + + docs = self.python_version_fallback(docs) self.docs_to_fetch = docs def index(self): @@ -140,3 +148,41 @@ def get_doc_by_slug(self, doc_slug): return doc return None + + def python_version_fallback(self, docs_to_fetch): + """ + If 'python' is present in docs_to_fetch (without version), + automatically replace it with the highest python~X.Y version from devdocs + """ + r = requests.get(DEVDOCS_INDEX_ALL_URL) + all_docs = r.json() + + python_slugs = [d['slug'] for d in all_docs if d['slug'].startswith('python~')] + + if not python_slugs: + return docs_to_fetch + + version_regex = re.compile(r'python~(\d+\.\d+)') + versions = [] + for slug in python_slugs: + match = version_regex.match(slug) + if match: + major_minor_str = match.group(1) + major_str, minor_str = major_minor_str.split('.') + major = int(major_str) + minor = int(minor_str) + versions.append((major, minor)) + if not versions: + return docs_to_fetch + + versions.sort() + (latest_major, latest_minor) = versions[-1] + latest_python_slug = f"python~{latest_major}.{latest_minor}" + + new_docs_to_fetch = [] + for slug in docs_to_fetch: + if slug == "python": + new_docs_to_fetch.append(latest_python_slug) + else: + new_docs_to_fetch.append(slug) + return new_docs_to_fetch From f38b0d296d5bba9c4d42ea5721615ba94ae82174 Mon Sep 17 00:00:00 2001 From: Metin Yalcinkaya Date: Sat, 11 Jan 2025 16:39:35 +1100 Subject: [PATCH 4/4] feat: Added generalised version matching for docs that require it Building on the Python versioning fallback, added a generalised way to deal with versioned docs (E.g: Python~3.13, gcc~14), and defaults to latest version is none is provided by the user. Uses regex matching to split between name and version. --- devdocs/devdocs_service.py | 74 ++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/devdocs/devdocs_service.py b/devdocs/devdocs_service.py index 87f24a9..535d12d 100644 --- a/devdocs/devdocs_service.py +++ b/devdocs/devdocs_service.py @@ -61,7 +61,7 @@ def set_docs_to_fetch(self, docs): except: docs = [] - docs = self.python_version_fallback(docs) + docs = self.version_fallback(docs) self.docs_to_fetch = docs def index(self): @@ -149,40 +149,52 @@ def get_doc_by_slug(self, doc_slug): return None - def python_version_fallback(self, docs_to_fetch): + def parse_version_to_tuple(self, version_str): """ - If 'python' is present in docs_to_fetch (without version), - automatically replace it with the highest python~X.Y version from devdocs + Parses version string (3.10 or 4.1.2) in to a tuple of ints for easy sorting + If segment can't be converted to an int (such as 'beta'), fallback to 0 or handle specially + """ + parts = [] + for part in version_str.split('.'): + try: + parts.append(int(part)) + except ValueError: + parts.append(0) + return tuple(parts) + + def version_fallback(self, docs_to_fetch): + """ + For any doc name in docs_to_fetch that doesn't contain '~' (unversioned), + find if DevDocs has one or more versioned docs that start with that base name + '~'. + Default to highest version among them """ r = requests.get(DEVDOCS_INDEX_ALL_URL) all_docs = r.json() - python_slugs = [d['slug'] for d in all_docs if d['slug'].startswith('python~')] - - if not python_slugs: - return docs_to_fetch - - version_regex = re.compile(r'python~(\d+\.\d+)') - versions = [] - for slug in python_slugs: - match = version_regex.match(slug) + base_map = {} + pattern = re.compile(r'^(.+?)~(.+)$') + for doc in all_docs: + slug = doc['slug'] + match = pattern.match(slug) if match: - major_minor_str = match.group(1) - major_str, minor_str = major_minor_str.split('.') - major = int(major_str) - minor = int(minor_str) - versions.append((major, minor)) - if not versions: - return docs_to_fetch - - versions.sort() - (latest_major, latest_minor) = versions[-1] - latest_python_slug = f"python~{latest_major}.{latest_minor}" - - new_docs_to_fetch = [] - for slug in docs_to_fetch: - if slug == "python": - new_docs_to_fetch.append(latest_python_slug) + base_name = match.group(1) + base_map.setdefault(base_name, []).append(slug) + for base, slugs in base_map.items(): + slug_version_pairs = [] + for s in slugs: + version_part = s.split('~', 1)[1] + slug_version_pairs.append((s, self.parse_version_to_tuple(version_part))) + slug_version_pairs.sort(key=lambda x: x[1]) + base_map[base] = [p[0] for p in slug_version_pairs] + new_docs = [] + for doc in docs_to_fetch: + if '~' in doc: + new_docs.append(doc) else: - new_docs_to_fetch.append(slug) - return new_docs_to_fetch + if doc in base_map: + highest_versioned_slug = base_map[doc][-1] + new_docs.append(highest_versioned_slug) + else: + new_docs.append(doc) + + return new_docs