refactor: use strict match for find

main
Xubai Wang 2022-02-22 15:01:14 +08:00
parent f23556739f
commit 004977e6e4
4 changed files with 70 additions and 57 deletions

View File

@ -74,10 +74,10 @@ def htmlOutput (result : AnalyzerResult) (root : String) : IO Unit := do
for (_, mod) in result.moduleInfo.toArray do for (_, mod) in result.moduleInfo.toArray do
for decl in filterMapDocInfo mod.members do for decl in filterMapDocInfo mod.members do
let name := decl.getName.toString let name := decl.getName.toString
let description := decl.getDocString.getD "" let doc := decl.getDocString.getD ""
let link := Id.run <| ReaderT.run (declNameToLink decl.getName) config let link := Id.run <| ReaderT.run (declNameToLink decl.getName) config
let source := Id.run <| ReaderT.run (getSourceUrl mod.name decl.getDeclarationRange) config let source := Id.run <| ReaderT.run (getSourceUrl mod.name decl.getDeclarationRange) config
let obj := Json.mkObj [("name", name), ("description", description), ("link", link), ("source", source)] let obj := Json.mkObj [("name", name), ("doc", doc), ("link", link), ("source", source)]
declList := declList.push obj declList := declList.push obj
let json := Json.arr declList let json := Json.arr declList

View File

@ -1,6 +1,6 @@
/** /**
* This module is a wrapper that facilitates manipulating the declaration data. * This module is a wrapper that facilitates manipulating the declaration data.
* *
* Please see {@link DeclarationDataCenter} for more information. * Please see {@link DeclarationDataCenter} for more information.
*/ */
@ -8,16 +8,16 @@ import { SITE_ROOT } from "./site-root.js";
/** /**
* The DeclarationDataCenter is used for declaration searching. * The DeclarationDataCenter is used for declaration searching.
* *
* For usage, see the {@link init} and {@link search} methods. * For usage, see the {@link init} and {@link search} methods.
*/ */
export class DeclarationDataCenter { export class DeclarationDataCenter {
/** /**
* The declaration data. Users should not interact directly with this field. * The declaration data. Users should not interact directly with this field.
* *
* *NOTE:* This is not made private to support legacy browsers. * *NOTE:* This is not made private to support legacy browsers.
*/ */
declarationData = []; declarationData = null;
/** /**
* Used to implement the singleton, in case we need to fetch data mutiple times in the same page. * Used to implement the singleton, in case we need to fetch data mutiple times in the same page.
@ -26,7 +26,7 @@ export class DeclarationDataCenter {
/** /**
* Construct a DeclarationDataCenter with given data. * Construct a DeclarationDataCenter with given data.
* *
* Please use {@link DeclarationDataCenter.init} instead, which automates the data fetching process. * Please use {@link DeclarationDataCenter.init} instead, which automates the data fetching process.
* @param {*} declarationData * @param {*} declarationData
*/ */
@ -46,13 +46,19 @@ export class DeclarationDataCenter {
); );
const response = await fetch(dataUrl); const response = await fetch(dataUrl);
const json = await response.json(); const json = await response.json();
const data = json.map(({ name, description, link, source }) => [ // the data is a map of name (original case) to declaration data.
name, const data = new Map(
name.toLowerCase(), json.map(({ name, doc, link, source: sourceLink }) => [
description.toLowerCase(), name,
link, {
source, name,
]); lowerName: name.toLowerCase(),
lowerDoc: doc.toLowerCase(),
link,
sourceLink,
},
])
);
DeclarationDataCenter.singleton = new DeclarationDataCenter(data); DeclarationDataCenter.singleton = new DeclarationDataCenter(data);
} }
return DeclarationDataCenter.singleton; return DeclarationDataCenter.singleton;
@ -66,8 +72,12 @@ export class DeclarationDataCenter {
if (!pattern) { if (!pattern) {
return []; return [];
} }
// TODO: implement strict if (strict) {
return getMatches(this.declarationData, pattern); let decl = this.declarationData.get(pattern);
return decl ? [decl] : [];
} else {
return getMatches(this.declarationData, pattern);
}
} }
} }
@ -75,20 +85,15 @@ function isSeparater(char) {
return char === "." || char === "_"; return char === "." || char === "_";
} }
function matchCaseSensitive( // HACK: the fuzzy matching is quite hacky
declName,
lowerDeclName, function matchCaseSensitive(declName, lowerDeclName, pattern) {
pattern
) {
let i = 0, let i = 0,
j = 0, j = 0,
err = 0, err = 0,
lastMatch = 0; lastMatch = 0;
while (i < declName.length && j < pattern.length) { while (i < declName.length && j < pattern.length) {
if ( if (pattern[j] === declName[i] || pattern[j] === lowerDeclName[i]) {
pattern[j] === declName[i] ||
pattern[j] === lowerDeclName[i]
) {
err += (isSeparater(pattern[j]) ? 0.125 : 1) * (i - lastMatch); err += (isSeparater(pattern[j]) ? 0.125 : 1) * (i - lastMatch);
if (pattern[j] !== declName[i]) err += 0.5; if (pattern[j] !== declName[i]) err += 0.5;
lastMatch = i + 1; lastMatch = i + 1;
@ -109,18 +114,24 @@ function getMatches(declarations, pattern, maxResults = 30) {
const lowerPats = pattern.toLowerCase().split(/\s/g); const lowerPats = pattern.toLowerCase().split(/\s/g);
const patNoSpaces = pattern.replace(/\s/g, ""); const patNoSpaces = pattern.replace(/\s/g, "");
const results = []; const results = [];
for (const [decl, lowerDecl, lowerDoc, link, source] of declarations) { for (const {
let err = matchCaseSensitive(decl, lowerDecl, patNoSpaces); name,
lowerName,
lowerDoc,
link,
sourceLink,
} of declarations.values()) {
let err = matchCaseSensitive(name, lowerName, patNoSpaces);
// match all words as substrings of docstring // match all words as substrings of docstring
if ( if (
!(err < 3) && err >= 3 &&
pattern.length > 3 && pattern.length > 3 &&
lowerPats.every((l) => lowerDoc.indexOf(l) != -1) lowerPats.every((l) => lowerDoc.indexOf(l) != -1)
) { ) {
err = 3; err = 3;
} }
if (err !== undefined) { if (err !== undefined) {
results.push({ decl, err, link, source }); results.push({ name, err, lowerName, lowerDoc, link, sourceLink });
} }
} }
return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults); return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults);

View File

@ -7,21 +7,19 @@ import { DeclarationDataCenter } from "../declaration-data.js";
async function findRedirect(pattern, isSource) { async function findRedirect(pattern, isSource) {
const dataCenter = await DeclarationDataCenter.init(); const dataCenter = await DeclarationDataCenter.init();
try { let results = dataCenter.search(pattern, true);
let results = dataCenter.search(pattern); if (results.length == 0) {
window.location.replace(isSource ? results[0].source : results[0].link); // if there is no exact match, redirect to 404 page to use fuzzy match
} catch { window.location.replace(`${SITE_ROOT}404.html#${pattern ?? ""}`);
window.location.replace(`${SITE_ROOT}404.html`); } else {
// redirect to doc or source page.
window.location.replace(isSource ? results[0].sourceLink : results[0].link);
} }
} }
let hash = window.location.hash.split("#"); let hash = window.location.hash.replace("#", "");
if (hash.length == 0) { if (hash.endsWith("/src")) {
window.location.replace(`${SITE_ROOT}/404.html`);
}
if (hash[1].endsWith("/src")) {
findRedirect(hash.replace("/src", ""), true); findRedirect(hash.replace("/src", ""), true);
} else { } else {
findRedirect(hash, false); findRedirect(hash, false);

View File

@ -6,9 +6,8 @@ import { DeclarationDataCenter } from "./declaration-data.js";
const HOW_ABOUT = document.querySelector("#howabout"); const HOW_ABOUT = document.querySelector("#howabout");
// Show url of the missing page
if (HOW_ABOUT) { if (HOW_ABOUT) {
HOW_ABOUT.innerText = "Please wait a second. I'll try to help you.";
HOW_ABOUT.parentNode HOW_ABOUT.parentNode
.insertBefore(document.createElement("pre"), HOW_ABOUT) .insertBefore(document.createElement("pre"), HOW_ABOUT)
.appendChild(document.createElement("code")).innerText = .appendChild(document.createElement("code")).innerText =
@ -17,19 +16,24 @@ if (HOW_ABOUT) {
// TODO: add how about functionality for similar page as well. // TODO: add how about functionality for similar page as well.
const pattern = window.location.hash.replace("#", ""); const pattern = window.location.hash.replace("#", "");
DeclarationDataCenter.init().then((dataCenter) => { // try to search for similar declarations
let results = dataCenter.search(pattern); if (pattern) {
if (results.length > 0) { HOW_ABOUT.innerText = "Please wait a second. I'll try to help you.";
HOW_ABOUT.innerText = "How about one of these instead:"; DeclarationDataCenter.init().then((dataCenter) => {
const ul = HOW_ABOUT.appendChild(document.createElement("ul")); let results = dataCenter.search(pattern);
for (const { decl, link } of results) { if (results.length > 0) {
const li = ul.appendChild(document.createElement("li")); HOW_ABOUT.innerText = "How about one of these instead:";
const a = li.appendChild(document.createElement("a")); const ul = HOW_ABOUT.appendChild(document.createElement("ul"));
a.href = link; for (const { name, link } of results) {
a.appendChild(document.createElement("code")).innerText = decl; const li = ul.appendChild(document.createElement("li"));
const a = li.appendChild(document.createElement("a"));
a.href = link;
a.appendChild(document.createElement("code")).innerText = name;
}
} else {
HOW_ABOUT.innerText =
"Sorry, I cannot find any similar declarations. Check the link or use the module navigation to find what you want :P";
} }
} else { });
HOW_ABOUT.innerText = "Sorry, I cannot find any similar declarations. Check the link or use the module navigation to find what you want :P"; }
}
});
} }