Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ raised, never by hand.
`inbox/migrate-to-aws.md`. Notes go under `[note] inbox`, `inbox` unless set,
and the date moves into a `created:` line in the note's front matter, which
`note ls` and the selector already read.
- **`cid config check` is now `cid doctor`.** The report is unchanged; the
old spelling is gone, so a setup script that runs it needs the new name.

## v0.23.0

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Rationale for code that does is a doc comment at the site — `ScratchRow`,
Prefer `Preview::Text` from data in hand — hence PR bodies from `gh pr list`
rather than `gh pr view`. A `Preview::Command` must be local, bounded, built
through `select::quote`, and `--no-optional-locks` if it is git.
- **A new dependency on the outside world gets a `config check` row** in
- **A new dependency on the outside world gets a `doctor` row** in
`cmd/config.rs`. `Fail` only when cid is genuinely broken without it, and
skip a check that repeats an earlier one.
- **A new setting gets a `config print` row**, under the table it is written
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ cargo install --git https://github.com/joakimen/cid
cid config init # write ~/.config/cid/config.toml, then set `root`
cid init <shell> # shell functions, key bindings and completions, to source
cid config print # every setting, and what is in force for it
cid config check # what cid reaches for, and what is missing
cid doctor # what cid reaches for, and what is missing
```

## Commands
Expand All @@ -55,7 +55,8 @@ cid config check # what cid reaches for, and what is missing
| `cid ps` | what is running, and what to kill |
| `cid history` | the commands you have already run, back onto the command line |
| `cid project` | builds or installs `$PWD`, whatever it turns out to be written in |
| `cid config` | write, read back and check the configuration |
| `cid config` | write and read back the configuration |
| `cid doctor` | what cid reaches for, and what is missing |
| `cid stats` | what you run, how often, and how long cid takes over it |
| `cid init` | the shell integration below |

Expand Down Expand Up @@ -83,7 +84,7 @@ b = "project-build" # cid project build

What a table holds is the whole of what is bound, so leaving a key out is how
it stays free. `cid config actions` lists every action with whatever is bound
to it, `cid config print` lists what you have, and `cid config check` says
to it, `cid config print` lists what you have, and `cid doctor` says
whether they all resolve — an action cid
does not define stops `cid init` rather than emitting a shell where one key
silently does nothing.
Expand Down
6 changes: 3 additions & 3 deletions src/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ pub fn resolve(config: &ShellConfig) -> Result<Integration> {
///
/// Unlike [`resolve`], an action nobody defines is kept rather than refused:
/// `cid config print` reports a line the file really has, and leaves calling
/// it broken to `cid config check`.
/// it broken to `cid doctor`.
pub fn entries(table: Option<&Bindings>) -> Vec<(&str, &str)> {
table
.into_iter()
Expand All @@ -273,7 +273,7 @@ pub struct Assignment<'a> {
/// the ones that are still free is what the list is for.
///
/// A line naming an action cid does not define belongs to no row here;
/// `cid config check` is where that is reported.
/// `cid doctor` is where that is reported.
pub fn assignments(config: &ShellConfig) -> Vec<Assignment<'_>> {
let bindings = entries(config.bindings.as_ref());
let aliases = entries(config.aliases.as_ref());
Expand Down Expand Up @@ -531,7 +531,7 @@ mod tests {
assert!(row("proc-kill").keys.is_empty());
}

/// Reporting a line that names nothing is `config check`'s job; here it
/// Reporting a line that names nothing is `doctor`'s job; here it
/// would be a row for an action that does not exist.
#[test]
fn a_line_naming_no_action_adds_no_row() {
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ ignore = ["node_modules", "target"]
# same configuration serves fish and any shell cid later learns to write for.
# Between them the two tables below name every action there is. `cid config
# actions` lists them all with whatever is bound to each, `cid config print`
# lists the keys and names you have, and `cid config check` says whether they
# lists the keys and names you have, and `cid doctor` says whether they
# resolve.
#
# cid binds nothing on its own. The two tables below are suggestions, and
Expand Down Expand Up @@ -602,7 +602,7 @@ fn pad(text: &str, width: usize) -> String {
/// `cid config print` — every setting there is, and what is in force for it.
///
/// The settings only: whether the paths they name exist, and whether the tools
/// cid shells out to are installed, is `cid config check`.
/// cid shells out to are installed, is `cid doctor`.
pub fn print(ctx: &Ctx) -> Result<()> {
ctx.log.info(&format!(
"printing configuration from {}",
Expand Down Expand Up @@ -1165,7 +1165,7 @@ fn files_check(ctx: &Ctx) -> Check {
}
}

/// Everything `config check` looks at, in the order it is reported: what the
/// Everything `doctor` looks at, in the order it is reported: what the
/// configuration points at, then the programs cid shells out to, then the
/// files it keeps.
fn collect(ctx: &Ctx) -> Vec<Section> {
Expand Down Expand Up @@ -1241,7 +1241,7 @@ fn collect(ctx: &Ctx) -> Vec<Section> {
]
}

/// `cid config check` — look at everything cid depends on in one go and say
/// `cid doctor` — look at everything cid depends on in one go and say
/// what is wrong with it. The exit status is non-zero only when something is
/// genuinely broken, so it is worth putting in a setup script.
///
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn ls(ctx: &Ctx, status: bool, missing: bool, exists: bool) -> Result<()> {

/// A file with its existence marked: green tick for there, red cross for gone.
/// Shared by `file ls --status` and `file prune`, and the same glyphs and
/// colour indices `config check` marks a passing and a failing row with.
/// colour indices `doctor` marks a passing and a failing row with.
fn status_row(path: &str, present: bool, color: bool) -> String {
let (glyph, tint) = if present { ("✓", 2) } else { ("✗", 1) };
term::paint(&format!("{glyph} {path}"), tint, color)
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ fn which(program: &str) -> Option<PathBuf> {
})
}

/// The `config check` row for the vault: where it is, how much is in it, and
/// The `doctor` row for the vault: where it is, how much is in it, and
/// how much of that a listing leaves out. One row rather than two, since a root
/// that resolves and holds nothing has already been reported by the count.
pub(crate) fn vault_summary(ctx: &Ctx) -> Result<Vault> {
Expand All @@ -1001,7 +1001,7 @@ pub(crate) fn vault_summary(ctx: &Ctx) -> Result<Vault> {
})
}

/// What `config check` found in the vault.
/// What `doctor` found in the vault.
pub(crate) struct Vault {
pub root: PathBuf,
/// Notes a listing shows.
Expand Down
2 changes: 1 addition & 1 deletion src/gh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ pub fn owners() -> Result<Vec<String>> {
/// token against the host, so this catches an expired or revoked one as well as
/// no login at all.
///
/// A network round trip, and therefore for `config check` alone — nothing on a
/// A network round trip, and therefore for `doctor` alone — nothing on a
/// keystroke path may ask this.
pub fn authenticated() -> bool {
let _child = stats::in_child();
Expand Down
39 changes: 20 additions & 19 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
//! Top-level commands: `repo`, `file`, `note`, `branch`, `worktree`, `pr`,
//! `ps` and `history` work with the things cid finds; `edit` opens a file
//! from the directory the user is in and `project` builds it; `config` manages
//! its configuration; `init` prints shell integration.
//! its configuration and `doctor` checks what it points at; `init` prints shell
//! integration.

use std::process::ExitCode;

Expand Down Expand Up @@ -242,6 +243,21 @@ enum Command {
#[command(subcommand)]
command: ConfigCmd,
},
/// Check everything cid depends on and report what is wrong
///
/// A checklist: the config file, the paths it names, the repositories
/// discovery actually finds, your editor, `git`, `gh` and whether it is
/// still logged in, fish's history file, the tracked-file list and the
/// notes vault — each a line, with what to do about the ones that are
/// wrong. Exits non-zero only when something is genuinely broken, so it is
/// worth putting in a setup script; a warning still leaves cid working.
///
/// What each setting is set to is `config print`; a row here repeats a
/// value only where repeating it is the way out of a problem.
///
/// The login is asked of GitHub, so this is the one command here that waits
/// on the network.
Doctor,
/// What you run, how often, and how long it takes
///
/// Every run appends one line to a log — the command, and how long it took
Expand All @@ -264,7 +280,7 @@ enum Command {
/// `[shell.aliases]`, which name actions rather than shell code. cid
/// binds nothing on its own: `cid config init` writes a suggested set out
/// commented, and until a table is written nothing is bound. `cid config
/// actions` lists every action there is to name, and `cid config check`
/// actions` lists every action there is to name, and `cid doctor`
/// says whether yours resolve; a configuration that will not parse,
/// or that names an action cid does not define, stops this command rather
/// than emitting a shell where one key silently does nothing.
Expand Down Expand Up @@ -900,7 +916,7 @@ enum ConfigCmd {
/// each one runs and what that action does; there are none until the file
/// names some.
///
/// Whether what the settings point at is actually there is `config check`.
/// Whether what the settings point at is actually there is `doctor`.
Print,
/// List every action a key binding or alias can name
///
Expand All @@ -913,21 +929,6 @@ enum ConfigCmd {
Actions,
/// Print the configuration file path
Path,
/// Check everything cid depends on and report what is wrong
///
/// A checklist: the config file, the paths it names, the repositories
/// discovery actually finds, your editor, `git`, `gh` and whether it is
/// still logged in, fish's history file, the tracked-file list and the
/// notes vault — each a line, with what to do about the ones that are
/// wrong. Exits non-zero only when something is genuinely broken, so it is
/// worth putting in a setup script; a warning still leaves cid working.
///
/// What each setting is set to is `config print`; a row here repeats a
/// value only where repeating it is the way out of a problem.
///
/// The login is asked of GitHub, so this is the one command here that waits
/// on the network.
Check,
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -1150,12 +1151,12 @@ fn dispatch(ctx: &Ctx, command: Command) -> anyhow::Result<()> {
ProjectCmd::Deps { dry_run, dump } => cmd::project::deps(ctx, dry_run, dump),
ProjectCmd::Build { dry_run } => cmd::project::build(ctx, dry_run),
},
Command::Doctor => cmd::config::check(ctx),
Command::Config { command } => match command {
ConfigCmd::Init { force } => cmd::config::init(ctx, force),
ConfigCmd::Print => cmd::config::print(ctx),
ConfigCmd::Actions => cmd::config::actions(ctx),
ConfigCmd::Path => cmd::config::path(ctx),
ConfigCmd::Check => cmd::config::check(ctx),
},
// The tree these report on is the clap command itself, which is the
// only place that knows every command there is.
Expand Down
36 changes: 18 additions & 18 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,12 @@ fn a_broken_config_names_the_file() {
}

#[test]
fn config_check_passes_on_a_sound_setup() {
fn doctor_passes_on_a_sound_setup() {
let sandbox = Sandbox::new();
mk_repo(&sandbox.home().join("dev/github.com"), "acme", "billing");
sandbox.write_config("[repo]\nroot = \"~/dev/github.com\"\n");

let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
run.ok();
assert!(run.stdout.contains("repo root"), "{}", run.stdout);
// Discovery is really run: the count is what answers "is my root right".
Expand All @@ -585,11 +585,11 @@ fn config_check_passes_on_a_sound_setup() {
}

#[test]
fn config_check_fails_on_a_missing_root() {
fn doctor_fails_on_a_missing_root() {
let sandbox = Sandbox::new();
sandbox.write_config("[repo]\nroot = \"~/not/here\"\n");

let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
run.code(1);
assert!(
run.stdout
Expand All @@ -602,14 +602,14 @@ fn config_check_fails_on_a_missing_root() {
assert!(!run.stdout.contains('\x1b'), "colour through a pipe");
}

/// `config check` and `--color` were written in parallel, each green on its
/// `doctor` and `--color` were written in parallel, each green on its
/// own branch, and the merge did not compile. Nothing tied the two together.
#[test]
fn config_check_honours_the_color_flag() {
fn doctor_honours_the_color_flag() {
let sandbox = Sandbox::new();
sandbox.write_config("[repo]\nroot = \"~/not/here\"\n");

let forced = sandbox.run(&["--color", "always", "config", "check"]);
let forced = sandbox.run(&["--color", "always", "doctor"]);
forced.code(1);
assert!(
forced.stdout.contains('\x1b'),
Expand All @@ -619,11 +619,11 @@ fn config_check_honours_the_color_flag() {
}

#[test]
fn config_check_does_not_report_one_problem_twice() {
fn doctor_does_not_report_one_problem_twice() {
let sandbox = Sandbox::new();
sandbox.write_config("[repo]\nroot = \"~/not/here\"\n");

let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
run.code(1);
assert_eq!(
run.stdout
Expand Down Expand Up @@ -2225,7 +2225,7 @@ fn note_ls_says_the_archives_are_why_it_found_nothing() {
/// An archive path is relative to the vault, and one that names nothing hides
/// nothing — which is a setting the user meant and did not get.
#[test]
fn config_check_reports_an_archive_that_is_not_there() {
fn doctor_reports_an_archive_that_is_not_there() {
let sandbox = Sandbox::new();
let vault = sandbox.home().join("notes");
mk_note(&vault, "work/plan.md", "plan\n", 1_000);
Expand All @@ -2234,7 +2234,7 @@ fn config_check_reports_an_archive_that_is_not_there() {
vault.display().to_string()
));

let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
assert!(run.stdout.contains("work/archive"), "{}", run.stdout);
}

Expand Down Expand Up @@ -2411,13 +2411,13 @@ fn note_open_falls_back_to_the_environment_editor() {
);
}

/// `config check` is what a setup script runs, so a configured vault has to
/// `doctor` is what a setup script runs, so a configured vault has to
/// report as found rather than as a thing cid knows nothing about.
#[test]
fn config_check_counts_the_notes_in_the_vault() {
fn doctor_counts_the_notes_in_the_vault() {
let sandbox = Sandbox::new();
mk_vault(&sandbox);
let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
assert!(run.stdout.contains("3 notes"), "{}", run.stdout);
assert!(run.stdout.contains("note editor"), "{}", run.stdout);
// `note open` shells out to it to search, so the report says whether it is
Expand Down Expand Up @@ -2620,7 +2620,7 @@ fn every_note_command_survives_a_vault_that_is_not_ascii() {
sandbox.run(&["note", "open", "øvelser.md"]).ok();
sandbox.run(&["note", "new", "Løsningsforslag"]).ok();
sandbox.run(&["note", "scratch"]).ok();
sandbox.run(&["config", "check"]);
sandbox.run(&["doctor"]);

// The one that crashed. Without a terminal it stops at the selector, which
// is past every byte offset that was the bug.
Expand Down Expand Up @@ -2961,7 +2961,7 @@ fn the_shell_integration_is_counted_by_check_and_named_by_print() {
let sandbox = Sandbox::new();
sandbox.write_config("[shell.aliases]\nbuild = \"project-build\"\n");

let check = sandbox.run(&["config", "check"]);
let check = sandbox.run(&["doctor"]);
let row = check
.lines()
.into_iter()
Expand All @@ -2980,11 +2980,11 @@ fn the_shell_integration_is_counted_by_check_and_named_by_print() {
}

#[test]
fn config_check_fails_on_a_binding_nothing_answers_to() {
fn doctor_fails_on_a_binding_nothing_answers_to() {
let sandbox = Sandbox::new();
sandbox.write_config("[shell.bindings]\nctrl-o = \"repo-jump\"\n");

let run = sandbox.run(&["config", "check"]);
let run = sandbox.run(&["doctor"]);
run.code(1);
assert!(
run.stdout.contains("repo-jump"),
Expand Down