@keyline-icons/cli 0.2.0 → 0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyline-icons/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Search Keyline Icons and copy them into your project from the terminal.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
package/src/index.mjs CHANGED
@@ -22,9 +22,11 @@ import { existsSync } from "node:fs"
22
22
  const data = JSON.parse(
23
23
  await readFile(new URL("../icons.json", import.meta.url), "utf8")
24
24
  )
25
- const { icons, styles, keywords = {} } = data
25
+ // `corners` defaults for a bundle generated before the axis existed, so an
26
+ // older icons.json still runs rather than reading `undefined.includes`.
27
+ const { icons, styles, corners = ["regular"], keywords = {} } = data
26
28
  const NAMES = Object.keys(icons)
27
- const VERSION = "0.2.0"
29
+ const VERSION = "0.3.0"
28
30
 
29
31
  /** Colour only when a human is looking. Piped output stays clean. */
30
32
  const tty = process.stdout.isTTY
@@ -49,7 +51,7 @@ function die(msg) {
49
51
  * positional, which is what lets `add a b c --style fill` work in any order.
50
52
  */
51
53
  function parseArgs(argv) {
52
- const alias = { s: "style", o: "out", l: "limit", h: "help", v: "version" }
54
+ const alias = { s: "style", c: "corners", o: "out", l: "limit", h: "help", v: "version" }
53
55
  const flags = {}
54
56
  const positional = []
55
57
  for (let i = 0; i < argv.length; i++) {
@@ -89,10 +91,25 @@ const styleOf = (flags) => {
89
91
  return s
90
92
  }
91
93
 
94
+ const cornersOf = (flags) => {
95
+ const k = flags.corners ?? "regular"
96
+ if (!corners.includes(k))
97
+ die(`unknown corner treatment \`${k}\`. One of: ${corners.join(", ")}`)
98
+ return k
99
+ }
100
+
92
101
  /* ------------------------------------------------------------------ icons */
93
102
 
94
- function svgFor(name, style) {
95
- const art = icons[name]?.[style]
103
+ /**
104
+ * A drawing in one style and one treatment.
105
+ *
106
+ * The rounded half sits at `icons[name][style]` where it has always sat, and
107
+ * the sharp half one level down under `sharp`, so a project pinned to an older
108
+ * bundle reads the same shape it read before.
109
+ */
110
+ function svgFor(name, style, corners = "regular") {
111
+ const art =
112
+ corners === "sharp" ? icons[name]?.sharp?.[style] : icons[name]?.[style]
96
113
  if (!art) return null
97
114
  const attrs = Object.entries(art.root)
98
115
  .map(([k, v]) => ` ${k}="${v}"`)
@@ -181,14 +198,14 @@ const wholeWord = (hay, needle) => {
181
198
  return false
182
199
  }
183
200
 
184
- function search(query, style, limit) {
201
+ function search(query, style, limit, corners = "regular") {
185
202
  const q = query.toLowerCase().trim()
186
203
  const words = wordsOf(query)
187
204
  if (!words.length) return []
188
205
 
189
206
  return NAMES.filter(
190
207
  (n) =>
191
- (!style || icons[n][style]) &&
208
+ (!style || svgFor(n, style, corners)) &&
192
209
  (foreign[q] === n ||
193
210
  wholeWord(n, q) ||
194
211
  words.every((w) => wholeWord(n, w)) ||
@@ -255,17 +272,20 @@ function nearest(name) {
255
272
  .map((h) => h.n)
256
273
  }
257
274
 
258
- function resolveOrDie(name, style) {
275
+ function resolveOrDie(name, style, corners = "regular") {
259
276
  if (!icons[name]) {
260
277
  const near = nearest(name)
261
278
  die(
262
279
  `no icon named \`${name}\`.${near.length ? ` Did you mean: ${near.join(", ")}?` : ""}`
263
280
  )
264
281
  }
265
- const svg = svgFor(name, style)
282
+ const svg = svgFor(name, style, corners)
266
283
  if (!svg) {
284
+ // `Object.keys` would list `sharp` here as though it were a fourth style,
285
+ // which is the axis leaking into a sentence about coverage.
286
+ const has = styles.filter((s) => svgFor(name, s, corners))
267
287
  die(
268
- `\`${name}\` has no ${style} style. It has: ${Object.keys(icons[name]).join(", ")}.\n` +
288
+ `\`${name}\` has no ${style} style. It has: ${has.join(", ")}.\n` +
269
289
  ` Duotone and fill need a fillable region, and this glyph has none.`
270
290
  )
271
291
  }
@@ -288,13 +308,14 @@ ${bold("COMMANDS")}
288
308
 
289
309
  ${bold("OPTIONS")}
290
310
  -s, --style <s> ${styles.join(" | ")} (default: stroke)
311
+ -c, --corners <c> ${corners.join(" | ")} (default: regular)
291
312
  -o, --out <dir> Where \`add\` writes (default: ./icons)
292
313
  -l, --limit <n> Max results for \`search\` (default: 25)
293
314
 
294
315
  ${bold("EXAMPLES")}
295
316
  keyline-icons search arrow
296
317
  keyline-icons search mail --style fill
297
- keyline-icons show check > check.svg
318
+ keyline-icons show check --corners sharp > check-sharp.svg
298
319
  keyline-icons add circle-arrow-down bell --out src/icons
299
320
  keyline-icons list | grep chart
300
321
 
@@ -309,7 +330,8 @@ const commands = {
309
330
 
310
331
  list({ flags }) {
311
332
  const style = flags.style ? styleOf(flags) : null
312
- const names = style ? NAMES.filter((n) => icons[n][style]) : NAMES
333
+ const treatment = cornersOf(flags)
334
+ const names = style ? NAMES.filter((n) => svgFor(n, style, treatment)) : NAMES
313
335
  for (const n of names) out(n)
314
336
  note(dim(`\n${names.length} icons${style ? ` in ${style}` : ""}`))
315
337
  },
@@ -329,7 +351,7 @@ const commands = {
329
351
  is not a number at all earns the default. */
330
352
  const raw = parseInt(flags.limit ?? "25", 10)
331
353
  const limit = Math.min(Math.max(Number.isNaN(raw) ? 25 : raw, 1), 200)
332
- const hits = search(query, style, limit)
354
+ const hits = search(query, style, limit, cornersOf(flags))
333
355
  if (!hits.length) {
334
356
  const near = nearest(query)
335
357
  die(
@@ -337,7 +359,7 @@ const commands = {
337
359
  )
338
360
  }
339
361
  for (const n of hits) {
340
- const has = styles.filter((s) => icons[n][s])
362
+ const has = styles.filter((s) => svgFor(n, s, cornersOf(flags)))
341
363
  out(`${n}${tty ? " " + dim(`[${has.join(", ")}]`) : ""}`)
342
364
  }
343
365
  note(dim(`\n${hits.length} shown${style ? ` in ${style}` : ""}`))
@@ -346,17 +368,27 @@ const commands = {
346
368
  show({ flags, positional }) {
347
369
  const [name] = positional
348
370
  if (!name) die("show needs an icon name. Try: keyline-icons show check")
349
- process.stdout.write(resolveOrDie(name, styleOf(flags)))
371
+ process.stdout.write(resolveOrDie(name, styleOf(flags), cornersOf(flags)))
350
372
  },
351
373
 
352
374
  async add({ flags, positional }) {
353
375
  if (!positional.length) die("add needs at least one icon name.")
354
376
  const style = styleOf(flags)
377
+ const treatment = cornersOf(flags)
355
378
  const dir = resolve(flags.out === true || !flags.out ? "icons" : flags.out)
356
379
 
380
+ // The treatment rides in the filename, for the reason the style does not:
381
+ // two treatments of one drawing are the same name and the same style, so
382
+ // adding both would otherwise write one file twice and leave whichever ran
383
+ // last. Rounded keeps the bare name, which is what everyone means by it.
384
+ const suffix = treatment === "regular" ? "" : `-${treatment}`
385
+
357
386
  // Every name is resolved before anything is written, so a typo in the
358
387
  // third of five does not leave two files on disk and an error.
359
- const files = positional.map((name) => [name, resolveOrDie(name, style)])
388
+ const files = positional.map((name) => [
389
+ `${name}${suffix}`,
390
+ resolveOrDie(name, style, treatment),
391
+ ])
360
392
 
361
393
  await mkdir(dir, { recursive: true })
362
394
  for (const [name, svg] of files) {
@@ -369,7 +401,8 @@ const commands = {
369
401
  }
370
402
  note(
371
403
  dim(
372
- `\n${files.length} ${style} icon${files.length === 1 ? "" : "s"} to ${dir}`
404
+ `\n${files.length} ${treatment === "sharp" ? "sharp " : ""}${style} ` +
405
+ `icon${files.length === 1 ? "" : "s"} to ${dir}`
373
406
  )
374
407
  )
375
408
  },