Skip to content

Commit

Permalink
Add style CLI command.
Browse files Browse the repository at this point in the history
  • Loading branch information
kitschpatrol committed Jun 4, 2024
1 parent 3995ffb commit 6b3dbd1
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 6 deletions.
22 changes: 22 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ yanki [command]
| `sync` | `<directory>` `[options]` | Perform a one-way synchronization from a local directory of Markdown files to the Anki database. Any Markdown files in subdirectories are included as well. _(Default command.)_ |
| `list` | | Utility command to list Yanki-created notes in the Anki database. |
| `delete` | | Utility command to manually delete Yanki-created notes in the Anki database. This is for advanced use cases, usually the `sync` command takes care of deleting files from Anki Database once they're removed from the local file system. |
| `style` | | Utility command to set the CSS stylesheet for all present and future Yanki-created notes. |

_See the sections below for more information on each subcommand._

Expand Down Expand Up @@ -318,6 +319,27 @@ yanki delete
| `--help` | `-h` | Show help | `boolean` | |
| `--version` | `-v` | Show version number | `boolean` | |

#### Subcommand: `yanki style`

Utility command to set the CSS stylesheet for all present and future Yanki-created notes.

Usage:

```txt
yanki style
```

| Option | Alias | Description | Type | Default |
| -------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------- |
| `--dry-run` | `-d` | Run without making any changes to the Anki database. See a report of what would have been done. | `boolean` | `false` |
| `--css` | `-c` | Path to the CSS stylesheet to set for all Yanki-created notes. If not provided, the default Anki stylesheet is used. | `string` | |
| `--anki-connect` | | Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information. | `string` | `"http://127.0.0.1:8765"` |
| `--anki-auto-launch` | | Attempt to open the desktop Anki.app if it's not already running. (Experimental, macOS only.) | `boolean` | `false` |
| `--json` | | Output the list of updated note types / models as JSON to stdout. | `boolean` | `false` |
| `--verbose` | | Enable verbose logging. | `boolean` | `false` |
| `--help` | `-h` | Show help | `boolean` | |
| `--version` | `-v` | Show version number | `boolean` | |

<!-- /cli-help -->

### Library
Expand Down
65 changes: 65 additions & 0 deletions src/bin/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cleanNotes, formatCleanReport } from '../lib/actions/clean'
import { formatListReport, listNotes } from '../lib/actions/list'
import { formatStyleReport, setStyle } from '../lib/actions/style'
import { formatSyncReport, syncFiles } from '../lib/actions/sync'
import log from '../lib/utilities/log'
import { urlToHostAndPort } from '../lib/utilities/string'
Expand All @@ -12,6 +13,8 @@ import {
verboseOption,
} from './options'
import { globby } from 'globby'
import fs from 'node:fs/promises'
import path from 'node:path'
import untildify from 'untildify'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
Expand Down Expand Up @@ -164,6 +167,68 @@ await yargsInstance
}
},
)
// `yanki style`
.command(
'style',
'Utility command to set the CSS stylesheet for all present and future Yanki-created notes.',
(yargs) =>
yargs
.option(dryRun)
.option('css', {
alias: 'c',
default: undefined,
describe:
'Path to the CSS stylesheet to set for all Yanki-created notes. If not provided, the default Anki stylesheet is used.',
type: 'string',
})
.options(ankiConnectOption)
.options(ankiAutoLaunchOption)
.option(jsonOption('Output the list of updated note types / models as JSON to stdout.'))
.option(verboseOption),
async ({ ankiAutoLaunch, ankiConnect, css, dryRun, json, verbose }) => {
const { host, port } = urlToHostAndPort(ankiConnect)

let loadedCss: string | undefined
if (css !== undefined) {
if (path.extname(css) !== '.css') {
log.error('The provided CSS file must have a .css extension.')
process.exitCode = 1
return
}

try {
loadedCss = await fs.readFile(css, 'utf8')
} catch (error) {
if (error instanceof Error) {
log.error(`Error loading CSS file: ${error.message}`)
} else {
log.error(`Unknown error loading CSS file: ${String(error)}`)
}

process.exitCode = 1
return
}
}

const report = await setStyle({
ankiConnectOptions: {
autoLaunch: ankiAutoLaunch,
host,
port,
},
css: loadedCss ?? undefined,
dryRun,
})

if (json) {
process.stdout.write(JSON.stringify(report, undefined, 2))
process.stdout.write('\n')
} else {
process.stderr.write(formatStyleReport(report, verbose))
process.stderr.write('\n')
}
},
)
.demandCommand(1)
.alias('h', 'help')
.version()
Expand Down
2 changes: 1 addition & 1 deletion src/lib/actions/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function formatCleanReport(report: CleanReport, verbose = false): string
const lines: string[] = []

lines.push(
`${report.dryRun ? 'Would have' : 'Successfully'} deleted ${report.deleted.length} ${plur('note', noteCount)} and ${report.decks.length} ${plur('deck', deckCount)} from Anki${report.dryRun ? '' : ` in ${prettyMilliseconds(report.duration)}`}`,
`${report.dryRun ? 'Will' : 'Successfully'} deleted ${report.deleted.length} ${plur('note', noteCount)} and ${report.decks.length} ${plur('deck', deckCount)} from Anki${report.dryRun ? '' : ` in ${prettyMilliseconds(report.duration)}`}`,
)

if (verbose) {
Expand Down
36 changes: 35 additions & 1 deletion src/lib/actions/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { defaultCss } from '../model/constants'
import { yankiModelNames } from '../model/model'
import { updateModelStyle } from './anki-connect'
import { deepmerge } from 'deepmerge-ts'
import plur from 'plur'
import prettyMilliseconds from 'pretty-ms'
import type { PartialDeep } from 'type-fest'
import { YankiConnect, type YankiConnectOptions, defaultYankiConnectOptions } from 'yanki-connect'

export type StyleOptions = {
Expand All @@ -17,14 +20,15 @@ export const defaultStyleOptions: StyleOptions = {
}

export type StyleReport = {
dryRun: boolean
duration: number
models: Array<{
action: 'unchanged' | 'updated'
name: string
}>
}

export async function setStyle(options: Partial<StyleOptions>): Promise<StyleReport> {
export async function setStyle(options: PartialDeep<StyleOptions>): Promise<StyleReport> {
const startTime = performance.now()

// Defaults
Expand All @@ -44,7 +48,37 @@ export async function setStyle(options: Partial<StyleOptions>): Promise<StyleRep
}

return {
dryRun,
duration: performance.now() - startTime,
models: modelsReport,
}
}

export function formatStyleReport(report: StyleReport, verbose = false): string {
const lines: string[] = []

const unchangedModels = report.models.filter((model) => model.action === 'unchanged')
const updatedModels = report.models.filter((model) => model.action === 'updated')

lines.push(
`${report.dryRun ? 'Will' : 'Successfully'} update ${updatedModels.length} ${plur('model', updatedModels.length)} and left ${unchangedModels.length} ${plur('model', unchangedModels.length)} unchanged${report.dryRun ? '' : ` in ${prettyMilliseconds(report.duration)}`}`,
)

if (verbose) {
if (updatedModels.length > 0) {
lines.push('', report.dryRun ? 'Models to update:' : 'Updated models:')
for (const model of updatedModels) {
lines.push(` ${model.name}`)
}
}

if (unchangedModels.length > 0) {
lines.push('', report.dryRun ? 'Models unchanged:' : 'Unchanged models:')
for (const model of unchangedModels) {
lines.push(` ${model.name}`)
}
}
}

return lines.join('\n')
}
2 changes: 1 addition & 1 deletion src/lib/actions/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export function formatSyncReport(report: SyncReport, verbose = false): string {
const totalSynced = synced.filter((note) => note.action !== 'deleted').length

lines.push(
`${report.dryRun ? 'Would have' : 'Successfully'} synced ${totalSynced} ${plur('note', totalSynced)} to Anki${report.dryRun ? '' : ` in ${prettyMilliseconds(report.duration)}`}`,
`${report.dryRun ? 'Will' : 'Successfully'} synced ${totalSynced} ${plur('note', totalSynced)} to Anki${report.dryRun ? '' : ` in ${prettyMilliseconds(report.duration)}`}`,
)

if (verbose) {
Expand Down
17 changes: 14 additions & 3 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
export { type CleanOptions, cleanNotes, defaultCleanOptions } from './actions/clean'
export { type ListOptions, defaultListOptions, listNotes } from './actions/list'
export { type StyleOptions, type StyleReport, defaultStyleOptions, setStyle } from './actions/style'
export {
type CleanOptions,
cleanNotes,
defaultCleanOptions,
formatCleanReport,
} from './actions/clean'
export { type ListOptions, defaultListOptions, formatListReport, listNotes } from './actions/list'
export {
type StyleOptions,
type StyleReport,
defaultStyleOptions,
formatStyleReport,
setStyle,
} from './actions/style'
export {
type SyncOptions,
defaultSyncOptions,
Expand Down

0 comments on commit 6b3dbd1

Please sign in to comment.