.
This commit is contained in:
@@ -6,6 +6,7 @@ Usage:
|
||||
python tools/search_md.py --root docs_md/articles --query "breadcrumbs:Brain>Diagnosis" --out results.json
|
||||
|
||||
Supported query keys: breadcrumbs, authors, pageKeywords, category, title, enhancedTitle, type
|
||||
You can also use key `global` to search across all relevant fields.
|
||||
Breadcrumbs query format: 'A>B>C' (matches files whose breadcrumbs contain that sequence in order)
|
||||
Authors query: substring match against author.value
|
||||
Keywords: substring match against pageKeywords
|
||||
@@ -101,6 +102,105 @@ def run_search(root: str, qkey: str, qval: str) -> List[Dict[str, Any]]:
|
||||
if not fm:
|
||||
continue
|
||||
matched = False
|
||||
# Helper to test non-breadcrumb simple fields using the same semantics
|
||||
def match_field_value(v, qval_local: str) -> bool:
|
||||
if v is None:
|
||||
return False
|
||||
# If v is a list, check each element for a match
|
||||
if isinstance(v, list):
|
||||
for elem in v:
|
||||
s = str(elem)
|
||||
if '*' in qval_local:
|
||||
if fnmatch.fnmatchcase(s.lower(), qval_local.lower()):
|
||||
return True
|
||||
else:
|
||||
if qval_local.strip().lower() in s.lower():
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
s = str(v)
|
||||
if '*' in qval_local:
|
||||
return fnmatch.fnmatchcase(s.lower(), qval_local.lower())
|
||||
else:
|
||||
return qval_local.strip().lower() in s.lower()
|
||||
|
||||
# Helper to evaluate a single clause (key, value) against this file's frontmatter
|
||||
def match_clause(cl_key: str, cl_val: str) -> bool:
|
||||
cl_key = (cl_key or '').strip()
|
||||
cl_val = (cl_val or '').strip()
|
||||
if not cl_key:
|
||||
cl_key = 'global'
|
||||
if cl_key == 'breadcrumbs':
|
||||
query_parts = [p.strip() for p in cl_val.split('>') if p.strip()]
|
||||
bcs = fm.get('breadcrumbs') or []
|
||||
if bcs and isinstance(bcs, list) and bcs and isinstance(bcs[0], dict):
|
||||
bcs_list = [d.get('name') or d.get('slug') or '' for d in bcs]
|
||||
else:
|
||||
bcs_list = bcs
|
||||
return match_breadcrumbs(bcs_list, query_parts)
|
||||
elif cl_key == 'authors':
|
||||
authors = fm.get('authors') or []
|
||||
return match_author(authors, cl_val)
|
||||
elif cl_key == 'pageKeywords':
|
||||
pk = fm.get('pageKeywords') or ''
|
||||
if isinstance(pk, list):
|
||||
tokens = [str(x) for x in pk]
|
||||
else:
|
||||
tokens = [t.strip() for t in str(pk).split(',') if t.strip()]
|
||||
if '*' in cl_val:
|
||||
for t in tokens:
|
||||
if fnmatch.fnmatchcase(t.lower(), cl_val.lower()):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
for t in tokens:
|
||||
if cl_val.strip().lower() in t.lower():
|
||||
return True
|
||||
return False
|
||||
elif cl_key == 'global':
|
||||
q = cl_val or ''
|
||||
# breadcrumbs
|
||||
bcs = fm.get('breadcrumbs') or []
|
||||
if isinstance(bcs, list) and bcs and isinstance(bcs[0], dict):
|
||||
bcs_list = [d.get('name') or d.get('slug') or '' for d in bcs]
|
||||
else:
|
||||
bcs_list = bcs
|
||||
if '>' in q:
|
||||
query_parts = [p.strip() for p in q.split('>') if p.strip()]
|
||||
if match_breadcrumbs(bcs_list, query_parts):
|
||||
return True
|
||||
else:
|
||||
for b in bcs_list:
|
||||
try:
|
||||
if q.strip().lower() in str(b).lower():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
# authors
|
||||
if match_author(fm.get('authors') or [], q):
|
||||
return True
|
||||
# pageKeywords
|
||||
pk = fm.get('pageKeywords') or ''
|
||||
if isinstance(pk, list):
|
||||
tokens = [str(x) for x in pk]
|
||||
else:
|
||||
tokens = [t.strip() for t in str(pk).split(',') if t.strip()]
|
||||
if '*' in q:
|
||||
for t in tokens:
|
||||
if fnmatch.fnmatchcase(t.lower(), q.lower()):
|
||||
return True
|
||||
else:
|
||||
for t in tokens:
|
||||
if q.strip().lower() in t.lower():
|
||||
return True
|
||||
# other fields
|
||||
for field in ['category', 'title', 'enhancedTitle', 'type', 'docid']:
|
||||
if match_field_value(fm.get(field), q):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return match_field_value(fm.get(cl_key), cl_val)
|
||||
|
||||
if qkey == 'breadcrumbs':
|
||||
# qval format: A>B>C
|
||||
query_parts = [p.strip() for p in qval.split('>') if p.strip()]
|
||||
@@ -111,6 +211,75 @@ def run_search(root: str, qkey: str, qval: str) -> List[Dict[str, Any]]:
|
||||
else:
|
||||
bcs_list = bcs
|
||||
matched = match_breadcrumbs(bcs_list, query_parts)
|
||||
elif qkey == 'or':
|
||||
# qval is a set of clauses separated by '||', '|', or ';' where each clause is key:val
|
||||
# Example: "title:stroke|authors:Smith|global:keyword"
|
||||
sep_norm = (qval or '').replace('||', '|').replace(';', '|')
|
||||
clauses = [c.strip() for c in sep_norm.split('|') if c.strip()]
|
||||
for cl in clauses:
|
||||
if ':' in cl:
|
||||
ck, cv = cl.split(':', 1)
|
||||
else:
|
||||
ck, cv = 'global', cl
|
||||
try:
|
||||
if match_clause(ck, cv):
|
||||
matched = True
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
elif qkey == 'global':
|
||||
q = qval or ''
|
||||
# breadcrumbs: if query contains '>' treat as sequence, else substring match
|
||||
bcs = fm.get('breadcrumbs') or []
|
||||
if isinstance(bcs, list) and bcs and isinstance(bcs[0], dict):
|
||||
bcs_list = [d.get('name') or d.get('slug') or '' for d in bcs]
|
||||
else:
|
||||
bcs_list = bcs
|
||||
if '>' in q:
|
||||
query_parts = [p.strip() for p in q.split('>') if p.strip()]
|
||||
if match_breadcrumbs(bcs_list, query_parts):
|
||||
matched = True
|
||||
else:
|
||||
# substring match on breadcrumb names
|
||||
for b in bcs_list:
|
||||
try:
|
||||
if q.strip().lower() in str(b).lower():
|
||||
matched = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# authors
|
||||
if not matched:
|
||||
authors = fm.get('authors') or []
|
||||
if match_author(authors, q):
|
||||
matched = True
|
||||
|
||||
# pageKeywords
|
||||
if not matched:
|
||||
pk = fm.get('pageKeywords') or ''
|
||||
tokens: List[str]
|
||||
if isinstance(pk, list):
|
||||
tokens = [str(x) for x in pk]
|
||||
else:
|
||||
tokens = [t.strip() for t in str(pk).split(',') if t.strip()]
|
||||
if '*' in q:
|
||||
for t in tokens:
|
||||
if fnmatch.fnmatchcase(t.lower(), q.lower()):
|
||||
matched = True
|
||||
break
|
||||
else:
|
||||
for t in tokens:
|
||||
if q.strip().lower() in t.lower():
|
||||
matched = True
|
||||
break
|
||||
|
||||
# other simple fields
|
||||
if not matched:
|
||||
for field in ['category', 'title', 'enhancedTitle', 'type', 'docid']:
|
||||
if match_field_value(fm.get(field), q):
|
||||
matched = True
|
||||
break
|
||||
elif qkey == 'authors':
|
||||
authors = fm.get('authors') or []
|
||||
matched = match_author(authors, qval)
|
||||
|
||||
@@ -80,7 +80,7 @@ def search_page() -> None: # build UI
|
||||
|
||||
with ui.row().style('gap: 12px; align-items: start;'):
|
||||
root_input = ui.input(value='docs_md/articles', label='Root directory', placeholder='docs_md/articles')
|
||||
key_select = ui.select(['breadcrumbs', 'authors', 'pageKeywords', 'category', 'title', 'enhancedTitle', 'type'], value='breadcrumbs', label='Query key')
|
||||
key_select = ui.select(['global', 'or', 'breadcrumbs', 'authors', 'pageKeywords', 'category', 'title', 'enhancedTitle', 'type'], value='breadcrumbs', label='Query key')
|
||||
qval_input = ui.input(value='', label='Query value', placeholder='e.g. Brain>Diagnosis or Smith or keyword')
|
||||
|
||||
with ui.row().style('gap: 12px; align-items: start; margin-top: 8px;'):
|
||||
|
||||
Reference in New Issue
Block a user