@coolicons/cli 5.2.0 → 5.4.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
@@ -1,8 +1,8 @@
1
1
  # @coolicons/cli
2
2
 
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>
3
+ <a href="https://coolicons.cool/?ref=npm-cli"><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/?ref=npm-cli) and copy them into your project from the terminal: 1,751 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
@@ -14,7 +14,7 @@ npx @coolicons/cli add calendar bell --style bulk --radius round --out src/icons
14
14
 
15
15
  | Command | |
16
16
  | --- | --- |
17
- | `search <query>` | Find icons by name or meaning, ranked like the search on coolicons.cool |
17
+ | `search <query>` | Find icons by name or meaning, ranked like the search on coolicons.cool. Corrects typos like `calender` when nothing matches |
18
18
  | `show <name>` | Print one icon's SVG to stdout |
19
19
  | `add <name...>` | Write SVG files into a folder |
20
20
  | `list` | Print every icon name |
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
 
@@ -70,15 +70,26 @@ function run({ command, words, options }) {
70
70
  if (command === "search") {
71
71
  const query = words.join(" ");
72
72
  if (!query) throw new LookupError("search needs a query, like: coolicons search date");
73
- const results = search(query, Number(options.limit ?? 20));
73
+ const { icons: results, corrected, query: used } = search(query, Number(options.limit ?? 20));
74
+ if (corrected.length) say(`Results for "${used}"`);
74
75
  if (options.json) return out(JSON.stringify(results.map((icon) => ({ ...icon, component: component(icon) })), null, 2));
76
+ const rows = (icons) => {
77
+ const width = Math.max(...icons.map((icon) => icon.name.length));
78
+ 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(", ")}` : ""}`);
79
+ };
75
80
  if (!results.length) {
76
- say(`No icon matches "${query}".`);
81
+ const partial = searchPartial(query, Number(options.limit ?? 20));
82
+ if (partial.length && !partial[0].missing.length) {
83
+ rows(partial);
84
+ say(`${partial.length} of ${iconSet().icons.length} icons`);
85
+ return;
86
+ }
87
+ say(`No icon matches "${query}".${partial.length ? " Weak matches by some words, check that they fit:" : ""}`);
88
+ if (partial.length) rows(partial);
77
89
  process.exitCode = 1;
78
90
  return;
79
91
  }
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(", ")}` : ""}`);
92
+ rows(results);
82
93
  say(`${results.length} of ${iconSet().icons.length} icons`);
83
94
  return;
84
95
  }
package/dist/core.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { matchIcons } from "./search.js";
2
+ import { matchIconsCorrected, 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"];
@@ -56,7 +56,24 @@ export function checkVariant(style = "line", radius = "regular") {
56
56
  }
57
57
 
58
58
  export function search(query, limit = 10) {
59
- return matchIcons(iconSet().icons, query).slice(0, limit);
59
+ const result = matchIconsCorrected(iconSet().icons, query);
60
+ return { ...result, icons: result.icons.slice(0, limit) };
61
+ }
62
+
63
+ const FILLER = new Set(["a", "an", "and", "as", "for", "icon", "icons", "in", "of", "on", "or", "the", "to", "with"]);
64
+
65
+ export function searchPartial(query, limit = 10) {
66
+ const terms = searchTerms(query).filter((term) => !FILLER.has(term));
67
+ if (!terms.length) return [];
68
+ return iconSet()
69
+ .icons.map((icon) => {
70
+ const scores = terms.map((term) => scoreIcon(icon, [term]));
71
+ return { icon, matched: terms.filter((_, i) => scores[i]), rank: scores.reduce((sum, score) => sum + score, 0) };
72
+ })
73
+ .filter((entry) => entry.matched.length)
74
+ .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))
75
+ .slice(0, limit)
76
+ .map(({ icon, matched }) => ({ ...icon, missing: terms.filter((term) => !matched.includes(term)) }));
60
77
  }
61
78
 
62
79
  function distance(a, b) {
@@ -74,7 +91,7 @@ function distance(a, b) {
74
91
  export function suggest(input, limit = 5) {
75
92
  const name = toName(input);
76
93
  if (!name) return [];
77
- const found = search(name.replaceAll("-", " "), limit).map((icon) => icon.name);
94
+ const found = search(name.replaceAll("-", " "), limit).icons.map((icon) => icon.name);
78
95
  if (found.length >= limit) return found;
79
96
  const near = iconSet()
80
97
  .icons.map((icon) => ({ name: icon.name, cost: distance(name, icon.name) }))