@coolicons/cli 5.2.0 → 5.3.0

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <a href="https://coolicons.cool"><img src="https://raw.githubusercontent.com/krystonschwarze/coolicons/main/.github/assets/banner.png" alt="coolicons: Every icon for your next project. Free, wherever you build." width="100%"></a>
4
4
 
5
- Search [coolicons](https://coolicons.cool) and copy them into your project from the terminal: 1,324 icons in five styles and three corner radii. No install, no dependencies, no network. The whole set ships inside the package.
5
+ Search [coolicons](https://coolicons.cool) and copy them into your project from the terminal: 1,550 icons in five styles and three corner radii. No install, no dependencies, no network. The whole set ships inside the package.
6
6
 
7
7
  ```sh
8
8
  npx @coolicons/cli search date
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
- import { component, findIcon, iconSet, LookupError, RADII, search, STYLES, svg } from "./core.js";
4
+ import { component, findIcon, iconSet, LookupError, RADII, search, searchPartial, STYLES, svg } from "./core.js";
5
5
 
6
6
  const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
7
7
 
@@ -72,13 +72,23 @@ function run({ command, words, options }) {
72
72
  if (!query) throw new LookupError("search needs a query, like: coolicons search date");
73
73
  const results = search(query, Number(options.limit ?? 20));
74
74
  if (options.json) return out(JSON.stringify(results.map((icon) => ({ ...icon, component: component(icon) })), null, 2));
75
+ const rows = (icons) => {
76
+ const width = Math.max(...icons.map((icon) => icon.name.length));
77
+ for (const icon of icons) out(`${icon.name.padEnd(width)} ${component(icon)} ${icon.category}${icon.missing?.length ? ` weak: no ${icon.missing.join(", ")}` : ""}${icon.keywords.length ? ` ${icon.keywords.join(", ")}` : ""}`);
78
+ };
75
79
  if (!results.length) {
76
- say(`No icon matches "${query}".`);
80
+ const partial = searchPartial(query, Number(options.limit ?? 20));
81
+ if (partial.length && !partial[0].missing.length) {
82
+ rows(partial);
83
+ say(`${partial.length} of ${iconSet().icons.length} icons`);
84
+ return;
85
+ }
86
+ say(`No icon matches "${query}".${partial.length ? " Weak matches by some words, check that they fit:" : ""}`);
87
+ if (partial.length) rows(partial);
77
88
  process.exitCode = 1;
78
89
  return;
79
90
  }
80
- const width = Math.max(...results.map((icon) => icon.name.length));
81
- for (const icon of results) out(`${icon.name.padEnd(width)} ${component(icon)} ${icon.category}${icon.keywords.length ? ` ${icon.keywords.join(", ")}` : ""}`);
91
+ rows(results);
82
92
  say(`${results.length} of ${iconSet().icons.length} icons`);
83
93
  return;
84
94
  }
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { matchIcons } from "./search.js";
2
+ import { matchIcons, scoreIcon, searchTerms } from "./search.js";
3
3
 
4
4
  export const STYLES = ["line", "duocolor", "duotone", "fill", "bulk"];
5
5
  export const RADII = ["sharp", "regular", "round"];
@@ -59,6 +59,22 @@ export function search(query, limit = 10) {
59
59
  return matchIcons(iconSet().icons, query).slice(0, limit);
60
60
  }
61
61
 
62
+ const FILLER = new Set(["a", "an", "and", "as", "for", "icon", "icons", "in", "of", "on", "or", "the", "to", "with"]);
63
+
64
+ export function searchPartial(query, limit = 10) {
65
+ const terms = searchTerms(query).filter((term) => !FILLER.has(term));
66
+ if (!terms.length) return [];
67
+ return iconSet()
68
+ .icons.map((icon) => {
69
+ const scores = terms.map((term) => scoreIcon(icon, [term]));
70
+ return { icon, matched: terms.filter((_, i) => scores[i]), rank: scores.reduce((sum, score) => sum + score, 0) };
71
+ })
72
+ .filter((entry) => entry.matched.length)
73
+ .sort((a, b) => b.matched.length - a.matched.length || b.rank - a.rank || a.icon.name.length - b.icon.name.length || a.icon.name.localeCompare(b.icon.name))
74
+ .slice(0, limit)
75
+ .map(({ icon, matched }) => ({ ...icon, missing: terms.filter((term) => !matched.includes(term)) }));
76
+ }
77
+
62
78
  function distance(a, b) {
63
79
  const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
64
80
  for (let i = 1; i <= a.length; i++) {