@hasna/terminal 2.3.0 → 2.3.2
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/dist/App.js +404 -0
- package/dist/Browse.js +79 -0
- package/dist/FuzzyPicker.js +47 -0
- package/dist/Onboarding.js +51 -0
- package/dist/Spinner.js +12 -0
- package/dist/StatusBar.js +49 -0
- package/dist/ai.js +322 -0
- package/dist/cache.js +41 -0
- package/dist/cli.js +64 -16
- package/dist/command-rewriter.js +64 -0
- package/dist/command-validator.js +86 -0
- package/dist/compression.js +107 -0
- package/dist/context-hints.js +275 -0
- package/dist/diff-cache.js +107 -0
- package/dist/discover.js +212 -0
- package/dist/economy.js +123 -0
- package/dist/expand-store.js +38 -0
- package/dist/file-cache.js +72 -0
- package/dist/file-index.js +62 -0
- package/dist/history.js +62 -0
- package/dist/lazy-executor.js +54 -0
- package/dist/line-dedup.js +59 -0
- package/dist/loop-detector.js +75 -0
- package/dist/mcp/install.js +98 -0
- package/dist/mcp/server.js +569 -0
- package/dist/noise-filter.js +86 -0
- package/dist/output-processor.js +129 -0
- package/dist/output-router.js +41 -0
- package/dist/output-store.js +111 -0
- package/dist/parsers/base.js +2 -0
- package/dist/parsers/build.js +64 -0
- package/dist/parsers/errors.js +101 -0
- package/dist/parsers/files.js +78 -0
- package/dist/parsers/git.js +99 -0
- package/dist/parsers/index.js +48 -0
- package/dist/parsers/tests.js +89 -0
- package/dist/providers/anthropic.js +39 -0
- package/dist/providers/base.js +4 -0
- package/dist/providers/cerebras.js +95 -0
- package/dist/providers/groq.js +95 -0
- package/dist/providers/index.js +73 -0
- package/dist/providers/xai.js +95 -0
- package/dist/recipes/model.js +20 -0
- package/dist/recipes/storage.js +136 -0
- package/dist/search/content-search.js +68 -0
- package/dist/search/file-search.js +61 -0
- package/dist/search/filters.js +34 -0
- package/dist/search/index.js +5 -0
- package/dist/search/semantic.js +320 -0
- package/dist/session-boot.js +59 -0
- package/dist/session-context.js +55 -0
- package/dist/sessions-db.js +173 -0
- package/dist/smart-display.js +286 -0
- package/dist/snapshots.js +51 -0
- package/dist/supervisor.js +112 -0
- package/dist/test-watchlist.js +131 -0
- package/dist/tool-profiles.js +122 -0
- package/dist/tree.js +94 -0
- package/dist/usage-cache.js +65 -0
- package/package.json +8 -1
- package/src/ai.ts +8 -0
- package/src/cli.tsx +57 -18
- package/src/output-processor.ts +6 -1
- package/src/output-store.ts +58 -12
- package/src/tool-profiles.ts +139 -0
- package/.claude/scheduled_tasks.lock +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- package/CONTRIBUTING.md +0 -80
- package/benchmarks/benchmark.mjs +0 -115
- package/imported_modules.txt +0 -0
- package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
- package/temp/rtk/.claude/agents/debugger.md +0 -519
- package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
- package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
- package/temp/rtk/.claude/agents/technical-writer.md +0 -355
- package/temp/rtk/.claude/commands/diagnose.md +0 -352
- package/temp/rtk/.claude/commands/test-routing.md +0 -362
- package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
- package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
- package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
- package/temp/rtk/.claude/rules/cli-testing.md +0 -526
- package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
- package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
- package/temp/rtk/.claude/skills/performance.md +0 -435
- package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
- package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
- package/temp/rtk/.claude/skills/repo-recap.md +0 -206
- package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
- package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
- package/temp/rtk/.claude/skills/security-guardian.md +0 -503
- package/temp/rtk/.claude/skills/ship.md +0 -404
- package/temp/rtk/.github/workflows/benchmark.yml +0 -34
- package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
- package/temp/rtk/.github/workflows/release-please.yml +0 -51
- package/temp/rtk/.github/workflows/release.yml +0 -343
- package/temp/rtk/.github/workflows/security-check.yml +0 -135
- package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
- package/temp/rtk/.release-please-manifest.json +0 -3
- package/temp/rtk/ARCHITECTURE.md +0 -1491
- package/temp/rtk/CHANGELOG.md +0 -640
- package/temp/rtk/CLAUDE.md +0 -605
- package/temp/rtk/CONTRIBUTING.md +0 -199
- package/temp/rtk/Cargo.lock +0 -1668
- package/temp/rtk/Cargo.toml +0 -64
- package/temp/rtk/Formula/rtk.rb +0 -43
- package/temp/rtk/INSTALL.md +0 -390
- package/temp/rtk/LICENSE +0 -21
- package/temp/rtk/README.md +0 -386
- package/temp/rtk/README_es.md +0 -159
- package/temp/rtk/README_fr.md +0 -197
- package/temp/rtk/README_ja.md +0 -159
- package/temp/rtk/README_ko.md +0 -159
- package/temp/rtk/README_zh.md +0 -167
- package/temp/rtk/ROADMAP.md +0 -15
- package/temp/rtk/SECURITY.md +0 -217
- package/temp/rtk/TEST_EXEC_TIME.md +0 -102
- package/temp/rtk/build.rs +0 -57
- package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
- package/temp/rtk/docs/FEATURES.md +0 -1410
- package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
- package/temp/rtk/docs/filter-workflow.md +0 -102
- package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
- package/temp/rtk/docs/tracking.md +0 -583
- package/temp/rtk/hooks/opencode-rtk.ts +0 -39
- package/temp/rtk/hooks/rtk-awareness.md +0 -29
- package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
- package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
- package/temp/rtk/install.sh +0 -124
- package/temp/rtk/release-please-config.json +0 -10
- package/temp/rtk/scripts/benchmark.sh +0 -592
- package/temp/rtk/scripts/check-installation.sh +0 -162
- package/temp/rtk/scripts/install-local.sh +0 -37
- package/temp/rtk/scripts/rtk-economics.sh +0 -137
- package/temp/rtk/scripts/test-all.sh +0 -561
- package/temp/rtk/scripts/test-aristote.sh +0 -227
- package/temp/rtk/scripts/test-tracking.sh +0 -79
- package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
- package/temp/rtk/scripts/validate-docs.sh +0 -73
- package/temp/rtk/src/aws_cmd.rs +0 -880
- package/temp/rtk/src/binlog.rs +0 -1645
- package/temp/rtk/src/cargo_cmd.rs +0 -1727
- package/temp/rtk/src/cc_economics.rs +0 -1157
- package/temp/rtk/src/ccusage.rs +0 -340
- package/temp/rtk/src/config.rs +0 -187
- package/temp/rtk/src/container.rs +0 -855
- package/temp/rtk/src/curl_cmd.rs +0 -134
- package/temp/rtk/src/deps.rs +0 -268
- package/temp/rtk/src/diff_cmd.rs +0 -367
- package/temp/rtk/src/discover/mod.rs +0 -274
- package/temp/rtk/src/discover/provider.rs +0 -388
- package/temp/rtk/src/discover/registry.rs +0 -2022
- package/temp/rtk/src/discover/report.rs +0 -202
- package/temp/rtk/src/discover/rules.rs +0 -667
- package/temp/rtk/src/display_helpers.rs +0 -402
- package/temp/rtk/src/dotnet_cmd.rs +0 -1771
- package/temp/rtk/src/dotnet_format_report.rs +0 -133
- package/temp/rtk/src/dotnet_trx.rs +0 -593
- package/temp/rtk/src/env_cmd.rs +0 -204
- package/temp/rtk/src/filter.rs +0 -462
- package/temp/rtk/src/filters/README.md +0 -52
- package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
- package/temp/rtk/src/filters/basedpyright.toml +0 -47
- package/temp/rtk/src/filters/biome.toml +0 -45
- package/temp/rtk/src/filters/brew-install.toml +0 -37
- package/temp/rtk/src/filters/composer-install.toml +0 -40
- package/temp/rtk/src/filters/df.toml +0 -16
- package/temp/rtk/src/filters/dotnet-build.toml +0 -64
- package/temp/rtk/src/filters/du.toml +0 -16
- package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
- package/temp/rtk/src/filters/gcc.toml +0 -49
- package/temp/rtk/src/filters/gcloud.toml +0 -22
- package/temp/rtk/src/filters/hadolint.toml +0 -24
- package/temp/rtk/src/filters/helm.toml +0 -29
- package/temp/rtk/src/filters/iptables.toml +0 -27
- package/temp/rtk/src/filters/jj.toml +0 -28
- package/temp/rtk/src/filters/jq.toml +0 -24
- package/temp/rtk/src/filters/make.toml +0 -41
- package/temp/rtk/src/filters/markdownlint.toml +0 -24
- package/temp/rtk/src/filters/mix-compile.toml +0 -27
- package/temp/rtk/src/filters/mix-format.toml +0 -15
- package/temp/rtk/src/filters/mvn-build.toml +0 -44
- package/temp/rtk/src/filters/oxlint.toml +0 -43
- package/temp/rtk/src/filters/ping.toml +0 -63
- package/temp/rtk/src/filters/pio-run.toml +0 -40
- package/temp/rtk/src/filters/poetry-install.toml +0 -50
- package/temp/rtk/src/filters/pre-commit.toml +0 -35
- package/temp/rtk/src/filters/ps.toml +0 -16
- package/temp/rtk/src/filters/quarto-render.toml +0 -41
- package/temp/rtk/src/filters/rsync.toml +0 -48
- package/temp/rtk/src/filters/shellcheck.toml +0 -27
- package/temp/rtk/src/filters/shopify-theme.toml +0 -29
- package/temp/rtk/src/filters/skopeo.toml +0 -45
- package/temp/rtk/src/filters/sops.toml +0 -16
- package/temp/rtk/src/filters/ssh.toml +0 -44
- package/temp/rtk/src/filters/stat.toml +0 -34
- package/temp/rtk/src/filters/swift-build.toml +0 -41
- package/temp/rtk/src/filters/systemctl-status.toml +0 -33
- package/temp/rtk/src/filters/terraform-plan.toml +0 -35
- package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
- package/temp/rtk/src/filters/tofu-init.toml +0 -38
- package/temp/rtk/src/filters/tofu-plan.toml +0 -35
- package/temp/rtk/src/filters/tofu-validate.toml +0 -17
- package/temp/rtk/src/filters/trunk-build.toml +0 -39
- package/temp/rtk/src/filters/ty.toml +0 -50
- package/temp/rtk/src/filters/uv-sync.toml +0 -37
- package/temp/rtk/src/filters/xcodebuild.toml +0 -99
- package/temp/rtk/src/filters/yamllint.toml +0 -25
- package/temp/rtk/src/find_cmd.rs +0 -598
- package/temp/rtk/src/format_cmd.rs +0 -386
- package/temp/rtk/src/gain.rs +0 -723
- package/temp/rtk/src/gh_cmd.rs +0 -1651
- package/temp/rtk/src/git.rs +0 -2012
- package/temp/rtk/src/go_cmd.rs +0 -592
- package/temp/rtk/src/golangci_cmd.rs +0 -254
- package/temp/rtk/src/grep_cmd.rs +0 -288
- package/temp/rtk/src/gt_cmd.rs +0 -810
- package/temp/rtk/src/hook_audit_cmd.rs +0 -283
- package/temp/rtk/src/hook_check.rs +0 -171
- package/temp/rtk/src/init.rs +0 -1859
- package/temp/rtk/src/integrity.rs +0 -537
- package/temp/rtk/src/json_cmd.rs +0 -231
- package/temp/rtk/src/learn/detector.rs +0 -628
- package/temp/rtk/src/learn/mod.rs +0 -119
- package/temp/rtk/src/learn/report.rs +0 -184
- package/temp/rtk/src/lint_cmd.rs +0 -694
- package/temp/rtk/src/local_llm.rs +0 -316
- package/temp/rtk/src/log_cmd.rs +0 -248
- package/temp/rtk/src/ls.rs +0 -324
- package/temp/rtk/src/main.rs +0 -2482
- package/temp/rtk/src/mypy_cmd.rs +0 -389
- package/temp/rtk/src/next_cmd.rs +0 -241
- package/temp/rtk/src/npm_cmd.rs +0 -236
- package/temp/rtk/src/parser/README.md +0 -267
- package/temp/rtk/src/parser/error.rs +0 -46
- package/temp/rtk/src/parser/formatter.rs +0 -336
- package/temp/rtk/src/parser/mod.rs +0 -311
- package/temp/rtk/src/parser/types.rs +0 -119
- package/temp/rtk/src/pip_cmd.rs +0 -302
- package/temp/rtk/src/playwright_cmd.rs +0 -479
- package/temp/rtk/src/pnpm_cmd.rs +0 -573
- package/temp/rtk/src/prettier_cmd.rs +0 -221
- package/temp/rtk/src/prisma_cmd.rs +0 -482
- package/temp/rtk/src/psql_cmd.rs +0 -382
- package/temp/rtk/src/pytest_cmd.rs +0 -384
- package/temp/rtk/src/read.rs +0 -217
- package/temp/rtk/src/rewrite_cmd.rs +0 -50
- package/temp/rtk/src/ruff_cmd.rs +0 -402
- package/temp/rtk/src/runner.rs +0 -271
- package/temp/rtk/src/summary.rs +0 -297
- package/temp/rtk/src/tee.rs +0 -405
- package/temp/rtk/src/telemetry.rs +0 -248
- package/temp/rtk/src/toml_filter.rs +0 -1655
- package/temp/rtk/src/tracking.rs +0 -1416
- package/temp/rtk/src/tree.rs +0 -209
- package/temp/rtk/src/tsc_cmd.rs +0 -259
- package/temp/rtk/src/utils.rs +0 -432
- package/temp/rtk/src/verify_cmd.rs +0 -47
- package/temp/rtk/src/vitest_cmd.rs +0 -385
- package/temp/rtk/src/wc_cmd.rs +0 -401
- package/temp/rtk/src/wget_cmd.rs +0 -260
- package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
- package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
- package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
- package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
- package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
- package/tsconfig.json +0 -15
|
@@ -1,2022 +0,0 @@
|
|
|
1
|
-
use lazy_static::lazy_static;
|
|
2
|
-
use regex::{Regex, RegexSet};
|
|
3
|
-
|
|
4
|
-
use super::rules::{IGNORED_EXACT, IGNORED_PREFIXES, PATTERNS, RULES};
|
|
5
|
-
|
|
6
|
-
/// Result of classifying a command.
|
|
7
|
-
#[derive(Debug, PartialEq)]
|
|
8
|
-
pub enum Classification {
|
|
9
|
-
Supported {
|
|
10
|
-
rtk_equivalent: &'static str,
|
|
11
|
-
category: &'static str,
|
|
12
|
-
estimated_savings_pct: f64,
|
|
13
|
-
status: super::report::RtkStatus,
|
|
14
|
-
},
|
|
15
|
-
Unsupported {
|
|
16
|
-
base_command: String,
|
|
17
|
-
},
|
|
18
|
-
Ignored,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/// Average token counts per category for estimation when no output_len available.
|
|
22
|
-
pub fn category_avg_tokens(category: &str, subcmd: &str) -> usize {
|
|
23
|
-
match category {
|
|
24
|
-
"Git" => match subcmd {
|
|
25
|
-
"log" | "diff" | "show" => 200,
|
|
26
|
-
_ => 40,
|
|
27
|
-
},
|
|
28
|
-
"Cargo" => match subcmd {
|
|
29
|
-
"test" => 500,
|
|
30
|
-
_ => 150,
|
|
31
|
-
},
|
|
32
|
-
"Tests" => 800,
|
|
33
|
-
"Files" => 100,
|
|
34
|
-
"Build" => 300,
|
|
35
|
-
"Infra" => 120,
|
|
36
|
-
"Network" => 150,
|
|
37
|
-
"GitHub" => 200,
|
|
38
|
-
"PackageManager" => 150,
|
|
39
|
-
_ => 150,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
lazy_static! {
|
|
44
|
-
static ref REGEX_SET: RegexSet = RegexSet::new(PATTERNS).expect("invalid regex patterns");
|
|
45
|
-
static ref COMPILED: Vec<Regex> = PATTERNS
|
|
46
|
-
.iter()
|
|
47
|
-
.map(|p| Regex::new(p).expect("invalid regex"))
|
|
48
|
-
.collect();
|
|
49
|
-
static ref ENV_PREFIX: Regex =
|
|
50
|
-
Regex::new(r"^(?:sudo\s+|env\s+|[A-Z_][A-Z0-9_]*=[^\s]*\s+)+").unwrap();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// Classify a single (already-split) command.
|
|
54
|
-
pub fn classify_command(cmd: &str) -> Classification {
|
|
55
|
-
let trimmed = cmd.trim();
|
|
56
|
-
if trimmed.is_empty() {
|
|
57
|
-
return Classification::Ignored;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check ignored
|
|
61
|
-
for exact in IGNORED_EXACT {
|
|
62
|
-
if trimmed == *exact {
|
|
63
|
-
return Classification::Ignored;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
for prefix in IGNORED_PREFIXES {
|
|
67
|
-
if trimmed.starts_with(prefix) {
|
|
68
|
-
return Classification::Ignored;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Strip env prefixes (sudo, env VAR=val, VAR=val)
|
|
73
|
-
let stripped = ENV_PREFIX.replace(trimmed, "");
|
|
74
|
-
let cmd_clean = stripped.trim();
|
|
75
|
-
if cmd_clean.is_empty() {
|
|
76
|
-
return Classification::Ignored;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Exclude cat/head/tail with redirect operators — these are writes, not reads (#315)
|
|
80
|
-
if cmd_clean.starts_with("cat ")
|
|
81
|
-
|| cmd_clean.starts_with("head ")
|
|
82
|
-
|| cmd_clean.starts_with("tail ")
|
|
83
|
-
{
|
|
84
|
-
let has_redirect = cmd_clean
|
|
85
|
-
.split_whitespace()
|
|
86
|
-
.skip(1)
|
|
87
|
-
.any(|t| t.starts_with('>') || t == "<" || t.starts_with(">>"));
|
|
88
|
-
if has_redirect {
|
|
89
|
-
return Classification::Unsupported {
|
|
90
|
-
base_command: cmd_clean
|
|
91
|
-
.split_whitespace()
|
|
92
|
-
.next()
|
|
93
|
-
.unwrap_or("cat")
|
|
94
|
-
.to_string(),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Fast check with RegexSet — take the last (most specific) match
|
|
100
|
-
let matches: Vec<usize> = REGEX_SET.matches(cmd_clean).into_iter().collect();
|
|
101
|
-
if let Some(&idx) = matches.last() {
|
|
102
|
-
let rule = &RULES[idx];
|
|
103
|
-
|
|
104
|
-
// Extract subcommand for savings override and status detection
|
|
105
|
-
let (savings, status) = if let Some(caps) = COMPILED[idx].captures(cmd_clean) {
|
|
106
|
-
if let Some(sub) = caps.get(1) {
|
|
107
|
-
let subcmd = sub.as_str();
|
|
108
|
-
// Check if this subcommand has a special status
|
|
109
|
-
let status = rule
|
|
110
|
-
.subcmd_status
|
|
111
|
-
.iter()
|
|
112
|
-
.find(|(s, _)| *s == subcmd)
|
|
113
|
-
.map(|(_, st)| *st)
|
|
114
|
-
.unwrap_or(super::report::RtkStatus::Existing);
|
|
115
|
-
|
|
116
|
-
// Check if this subcommand has custom savings
|
|
117
|
-
let savings = rule
|
|
118
|
-
.subcmd_savings
|
|
119
|
-
.iter()
|
|
120
|
-
.find(|(s, _)| *s == subcmd)
|
|
121
|
-
.map(|(_, pct)| *pct)
|
|
122
|
-
.unwrap_or(rule.savings_pct);
|
|
123
|
-
|
|
124
|
-
(savings, status)
|
|
125
|
-
} else {
|
|
126
|
-
(rule.savings_pct, super::report::RtkStatus::Existing)
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
(rule.savings_pct, super::report::RtkStatus::Existing)
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
Classification::Supported {
|
|
133
|
-
rtk_equivalent: rule.rtk_cmd,
|
|
134
|
-
category: rule.category,
|
|
135
|
-
estimated_savings_pct: savings,
|
|
136
|
-
status,
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
// Extract base command for unsupported
|
|
140
|
-
let base = extract_base_command(cmd_clean);
|
|
141
|
-
if base.is_empty() {
|
|
142
|
-
Classification::Ignored
|
|
143
|
-
} else {
|
|
144
|
-
Classification::Unsupported {
|
|
145
|
-
base_command: base.to_string(),
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/// Extract the base command (first word, or first two if it looks like a subcommand pattern).
|
|
152
|
-
fn extract_base_command(cmd: &str) -> &str {
|
|
153
|
-
let parts: Vec<&str> = cmd.splitn(3, char::is_whitespace).collect();
|
|
154
|
-
match parts.len() {
|
|
155
|
-
0 => "",
|
|
156
|
-
1 => parts[0],
|
|
157
|
-
_ => {
|
|
158
|
-
let second = parts[1];
|
|
159
|
-
// If the second token looks like a subcommand (no leading -)
|
|
160
|
-
if !second.starts_with('-') && !second.contains('/') && !second.contains('.') {
|
|
161
|
-
// Return "cmd subcmd"
|
|
162
|
-
let end = cmd
|
|
163
|
-
.find(char::is_whitespace)
|
|
164
|
-
.and_then(|i| {
|
|
165
|
-
let rest = &cmd[i..];
|
|
166
|
-
let trimmed = rest.trim_start();
|
|
167
|
-
trimmed
|
|
168
|
-
.find(char::is_whitespace)
|
|
169
|
-
.map(|j| i + (rest.len() - trimmed.len()) + j)
|
|
170
|
-
})
|
|
171
|
-
.unwrap_or(cmd.len());
|
|
172
|
-
&cmd[..end]
|
|
173
|
-
} else {
|
|
174
|
-
parts[0]
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/// Split a command chain on `&&`, `||`, `;` outside quotes.
|
|
181
|
-
/// For pipes `|`, only keep the first command.
|
|
182
|
-
/// Lines with `<<` (heredoc) or `$((` are returned whole.
|
|
183
|
-
pub fn split_command_chain(cmd: &str) -> Vec<&str> {
|
|
184
|
-
let trimmed = cmd.trim();
|
|
185
|
-
if trimmed.is_empty() {
|
|
186
|
-
return vec![];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Heredoc or arithmetic expansion: treat as single command
|
|
190
|
-
if trimmed.contains("<<") || trimmed.contains("$((") {
|
|
191
|
-
return vec![trimmed];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
let mut results = Vec::new();
|
|
195
|
-
let mut start = 0;
|
|
196
|
-
let bytes = trimmed.as_bytes();
|
|
197
|
-
let len = bytes.len();
|
|
198
|
-
let mut i = 0;
|
|
199
|
-
let mut in_single = false;
|
|
200
|
-
let mut in_double = false;
|
|
201
|
-
let mut pipe_seen = false;
|
|
202
|
-
|
|
203
|
-
while i < len {
|
|
204
|
-
let b = bytes[i];
|
|
205
|
-
match b {
|
|
206
|
-
b'\'' if !in_double => {
|
|
207
|
-
in_single = !in_single;
|
|
208
|
-
i += 1;
|
|
209
|
-
}
|
|
210
|
-
b'"' if !in_single => {
|
|
211
|
-
in_double = !in_double;
|
|
212
|
-
i += 1;
|
|
213
|
-
}
|
|
214
|
-
b'|' if !in_single && !in_double => {
|
|
215
|
-
if i + 1 < len && bytes[i + 1] == b'|' {
|
|
216
|
-
// ||
|
|
217
|
-
let segment = trimmed[start..i].trim();
|
|
218
|
-
if !segment.is_empty() {
|
|
219
|
-
results.push(segment);
|
|
220
|
-
}
|
|
221
|
-
i += 2;
|
|
222
|
-
start = i;
|
|
223
|
-
} else {
|
|
224
|
-
// pipe: keep only first command
|
|
225
|
-
let segment = trimmed[start..i].trim();
|
|
226
|
-
if !segment.is_empty() {
|
|
227
|
-
results.push(segment);
|
|
228
|
-
}
|
|
229
|
-
pipe_seen = true;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
b'&' if !in_single && !in_double && i + 1 < len && bytes[i + 1] == b'&' => {
|
|
234
|
-
let segment = trimmed[start..i].trim();
|
|
235
|
-
if !segment.is_empty() {
|
|
236
|
-
results.push(segment);
|
|
237
|
-
}
|
|
238
|
-
i += 2;
|
|
239
|
-
start = i;
|
|
240
|
-
}
|
|
241
|
-
b';' if !in_single && !in_double => {
|
|
242
|
-
let segment = trimmed[start..i].trim();
|
|
243
|
-
if !segment.is_empty() {
|
|
244
|
-
results.push(segment);
|
|
245
|
-
}
|
|
246
|
-
i += 1;
|
|
247
|
-
start = i;
|
|
248
|
-
}
|
|
249
|
-
_ => {
|
|
250
|
-
i += 1;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if !pipe_seen && start < len {
|
|
256
|
-
let segment = trimmed[start..].trim();
|
|
257
|
-
if !segment.is_empty() {
|
|
258
|
-
results.push(segment);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
results
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/// Check if a command has RTK_DISABLED= prefix in its env prefix portion.
|
|
266
|
-
pub fn has_rtk_disabled_prefix(cmd: &str) -> bool {
|
|
267
|
-
let trimmed = cmd.trim();
|
|
268
|
-
let stripped = ENV_PREFIX.replace(trimmed, "");
|
|
269
|
-
let prefix_len = trimmed.len() - stripped.len();
|
|
270
|
-
let prefix_part = &trimmed[..prefix_len];
|
|
271
|
-
prefix_part.contains("RTK_DISABLED=")
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/// Strip RTK_DISABLED=X and other env prefixes, return the actual command.
|
|
275
|
-
pub fn strip_disabled_prefix(cmd: &str) -> &str {
|
|
276
|
-
let trimmed = cmd.trim();
|
|
277
|
-
let stripped = ENV_PREFIX.replace(trimmed, "");
|
|
278
|
-
// stripped is a Cow<str> that borrows from trimmed when no replacement happens.
|
|
279
|
-
// We need to return a &str into the original, so compute the offset.
|
|
280
|
-
let prefix_len = trimmed.len() - stripped.len();
|
|
281
|
-
trimmed[prefix_len..].trim_start()
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/// Rewrite a raw command to its RTK equivalent.
|
|
285
|
-
///
|
|
286
|
-
/// Returns `Some(rewritten)` if the command has an RTK equivalent or is already RTK.
|
|
287
|
-
/// Returns `None` if the command is unsupported or ignored (hook should pass through).
|
|
288
|
-
///
|
|
289
|
-
/// Handles compound commands (`&&`, `||`, `;`) by rewriting each segment independently.
|
|
290
|
-
/// For pipes (`|`), only rewrites the first command (the filter stays raw).
|
|
291
|
-
pub fn rewrite_command(cmd: &str, excluded: &[String]) -> Option<String> {
|
|
292
|
-
let trimmed = cmd.trim();
|
|
293
|
-
if trimmed.is_empty() {
|
|
294
|
-
return None;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Heredoc or arithmetic expansion — unsafe to split/rewrite
|
|
298
|
-
if trimmed.contains("<<") || trimmed.contains("$((") {
|
|
299
|
-
return None;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Simple (non-compound) already-RTK command — return as-is.
|
|
303
|
-
// For compound commands that start with "rtk" (e.g. "rtk git add . && cargo test"),
|
|
304
|
-
// fall through to rewrite_compound so the remaining segments get rewritten.
|
|
305
|
-
let has_compound = trimmed.contains("&&")
|
|
306
|
-
|| trimmed.contains("||")
|
|
307
|
-
|| trimmed.contains(';')
|
|
308
|
-
|| trimmed.contains('|')
|
|
309
|
-
|| trimmed.contains(" & ");
|
|
310
|
-
if !has_compound && (trimmed.starts_with("rtk ") || trimmed == "rtk") {
|
|
311
|
-
return Some(trimmed.to_string());
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
rewrite_compound(trimmed, excluded)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/// Rewrite a compound command (with `&&`, `||`, `;`, `|`) by rewriting each segment.
|
|
318
|
-
fn rewrite_compound(cmd: &str, excluded: &[String]) -> Option<String> {
|
|
319
|
-
let bytes = cmd.as_bytes();
|
|
320
|
-
let len = bytes.len();
|
|
321
|
-
let mut result = String::with_capacity(len + 32);
|
|
322
|
-
let mut any_changed = false;
|
|
323
|
-
let mut seg_start = 0;
|
|
324
|
-
let mut i = 0;
|
|
325
|
-
let mut in_single = false;
|
|
326
|
-
let mut in_double = false;
|
|
327
|
-
|
|
328
|
-
while i < len {
|
|
329
|
-
let b = bytes[i];
|
|
330
|
-
match b {
|
|
331
|
-
b'\'' if !in_double => {
|
|
332
|
-
in_single = !in_single;
|
|
333
|
-
i += 1;
|
|
334
|
-
}
|
|
335
|
-
b'"' if !in_single => {
|
|
336
|
-
in_double = !in_double;
|
|
337
|
-
i += 1;
|
|
338
|
-
}
|
|
339
|
-
b'|' if !in_single && !in_double => {
|
|
340
|
-
if i + 1 < len && bytes[i + 1] == b'|' {
|
|
341
|
-
// `||` operator — rewrite left, continue
|
|
342
|
-
let seg = cmd[seg_start..i].trim();
|
|
343
|
-
let rewritten =
|
|
344
|
-
rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
345
|
-
if rewritten != seg {
|
|
346
|
-
any_changed = true;
|
|
347
|
-
}
|
|
348
|
-
result.push_str(&rewritten);
|
|
349
|
-
result.push_str(" || ");
|
|
350
|
-
i += 2;
|
|
351
|
-
while i < len && bytes[i] == b' ' {
|
|
352
|
-
i += 1;
|
|
353
|
-
}
|
|
354
|
-
seg_start = i;
|
|
355
|
-
} else {
|
|
356
|
-
// `|` pipe — rewrite first segment only, pass through the rest unchanged
|
|
357
|
-
let seg = cmd[seg_start..i].trim();
|
|
358
|
-
let rewritten =
|
|
359
|
-
rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
360
|
-
if rewritten != seg {
|
|
361
|
-
any_changed = true;
|
|
362
|
-
}
|
|
363
|
-
result.push_str(&rewritten);
|
|
364
|
-
// Preserve the space before the pipe that was lost by trim()
|
|
365
|
-
result.push(' ');
|
|
366
|
-
result.push_str(cmd[i..].trim_start());
|
|
367
|
-
return if any_changed { Some(result) } else { None };
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
b'&' if !in_single && !in_double && i + 1 < len && bytes[i + 1] == b'&' => {
|
|
371
|
-
// `&&` operator — rewrite left, continue
|
|
372
|
-
let seg = cmd[seg_start..i].trim();
|
|
373
|
-
let rewritten = rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
374
|
-
if rewritten != seg {
|
|
375
|
-
any_changed = true;
|
|
376
|
-
}
|
|
377
|
-
result.push_str(&rewritten);
|
|
378
|
-
result.push_str(" && ");
|
|
379
|
-
i += 2;
|
|
380
|
-
while i < len && bytes[i] == b' ' {
|
|
381
|
-
i += 1;
|
|
382
|
-
}
|
|
383
|
-
seg_start = i;
|
|
384
|
-
}
|
|
385
|
-
b'&' if !in_single && !in_double => {
|
|
386
|
-
// #346: redirect detection — 2>&1 / >&2 (> before &) or &>file / &>>file (> after &)
|
|
387
|
-
let is_redirect =
|
|
388
|
-
(i > 0 && bytes[i - 1] == b'>') || (i + 1 < len && bytes[i + 1] == b'>');
|
|
389
|
-
if is_redirect {
|
|
390
|
-
i += 1;
|
|
391
|
-
} else {
|
|
392
|
-
// single `&` background execution operator
|
|
393
|
-
let seg = cmd[seg_start..i].trim();
|
|
394
|
-
let rewritten =
|
|
395
|
-
rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
396
|
-
if rewritten != seg {
|
|
397
|
-
any_changed = true;
|
|
398
|
-
}
|
|
399
|
-
result.push_str(&rewritten);
|
|
400
|
-
result.push_str(" & ");
|
|
401
|
-
i += 1;
|
|
402
|
-
while i < len && bytes[i] == b' ' {
|
|
403
|
-
i += 1;
|
|
404
|
-
}
|
|
405
|
-
seg_start = i;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
b';' if !in_single && !in_double => {
|
|
409
|
-
// `;` separator
|
|
410
|
-
let seg = cmd[seg_start..i].trim();
|
|
411
|
-
let rewritten = rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
412
|
-
if rewritten != seg {
|
|
413
|
-
any_changed = true;
|
|
414
|
-
}
|
|
415
|
-
result.push_str(&rewritten);
|
|
416
|
-
result.push(';');
|
|
417
|
-
i += 1;
|
|
418
|
-
while i < len && bytes[i] == b' ' {
|
|
419
|
-
i += 1;
|
|
420
|
-
}
|
|
421
|
-
if i < len {
|
|
422
|
-
result.push(' ');
|
|
423
|
-
}
|
|
424
|
-
seg_start = i;
|
|
425
|
-
}
|
|
426
|
-
_ => {
|
|
427
|
-
i += 1;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Last (or only) segment
|
|
433
|
-
let seg = cmd[seg_start..len].trim();
|
|
434
|
-
let rewritten = rewrite_segment(seg, excluded).unwrap_or_else(|| seg.to_string());
|
|
435
|
-
if rewritten != seg {
|
|
436
|
-
any_changed = true;
|
|
437
|
-
}
|
|
438
|
-
result.push_str(&rewritten);
|
|
439
|
-
|
|
440
|
-
if any_changed {
|
|
441
|
-
Some(result)
|
|
442
|
-
} else {
|
|
443
|
-
None
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/// Rewrite `head -N file` → `rtk read file --max-lines N`.
|
|
448
|
-
/// Returns `None` if the command doesn't match this pattern (fall through to generic logic).
|
|
449
|
-
fn rewrite_head_numeric(cmd: &str) -> Option<String> {
|
|
450
|
-
// Match: head -<digits> <file> (with optional env prefix)
|
|
451
|
-
lazy_static! {
|
|
452
|
-
static ref HEAD_N: Regex = Regex::new(r"^head\s+-(\d+)\s+(.+)$").expect("valid regex");
|
|
453
|
-
static ref HEAD_LINES: Regex =
|
|
454
|
-
Regex::new(r"^head\s+--lines=(\d+)\s+(.+)$").expect("valid regex");
|
|
455
|
-
}
|
|
456
|
-
if let Some(caps) = HEAD_N.captures(cmd) {
|
|
457
|
-
let n = caps.get(1)?.as_str();
|
|
458
|
-
let file = caps.get(2)?.as_str();
|
|
459
|
-
return Some(format!("rtk read {} --max-lines {}", file, n));
|
|
460
|
-
}
|
|
461
|
-
if let Some(caps) = HEAD_LINES.captures(cmd) {
|
|
462
|
-
let n = caps.get(1)?.as_str();
|
|
463
|
-
let file = caps.get(2)?.as_str();
|
|
464
|
-
return Some(format!("rtk read {} --max-lines {}", file, n));
|
|
465
|
-
}
|
|
466
|
-
// head with any other flag (e.g. -c, -q): skip rewriting to avoid clap errors
|
|
467
|
-
if cmd.starts_with("head -") {
|
|
468
|
-
return None;
|
|
469
|
-
}
|
|
470
|
-
None
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/// Rewrite `tail` numeric line forms to `rtk read ... --tail-lines N`.
|
|
474
|
-
/// Returns `None` when the pattern is unsupported (caller falls through / skips rewrite).
|
|
475
|
-
fn rewrite_tail_lines(cmd: &str) -> Option<String> {
|
|
476
|
-
lazy_static! {
|
|
477
|
-
static ref TAIL_N: Regex = Regex::new(r"^tail\s+-(\d+)\s+(.+)$").expect("valid regex");
|
|
478
|
-
static ref TAIL_N_SPACE: Regex =
|
|
479
|
-
Regex::new(r"^tail\s+-n\s+(\d+)\s+(.+)$").expect("valid regex");
|
|
480
|
-
static ref TAIL_LINES_EQ: Regex =
|
|
481
|
-
Regex::new(r"^tail\s+--lines=(\d+)\s+(.+)$").expect("valid regex");
|
|
482
|
-
static ref TAIL_LINES_SPACE: Regex =
|
|
483
|
-
Regex::new(r"^tail\s+--lines\s+(\d+)\s+(.+)$").expect("valid regex");
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
for re in [
|
|
487
|
-
&*TAIL_N,
|
|
488
|
-
&*TAIL_N_SPACE,
|
|
489
|
-
&*TAIL_LINES_EQ,
|
|
490
|
-
&*TAIL_LINES_SPACE,
|
|
491
|
-
] {
|
|
492
|
-
if let Some(caps) = re.captures(cmd) {
|
|
493
|
-
let n = caps.get(1)?.as_str();
|
|
494
|
-
let file = caps.get(2)?.as_str();
|
|
495
|
-
return Some(format!("rtk read {} --tail-lines {}", file, n));
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Unknown tail form: skip rewrite to preserve native behavior.
|
|
500
|
-
None
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/// Rewrite a single (non-compound) command segment.
|
|
504
|
-
/// Returns `Some(rewritten)` if matched (including already-RTK pass-through).
|
|
505
|
-
/// Returns `None` if no match (caller uses original segment).
|
|
506
|
-
fn rewrite_segment(seg: &str, excluded: &[String]) -> Option<String> {
|
|
507
|
-
let trimmed = seg.trim();
|
|
508
|
-
if trimmed.is_empty() {
|
|
509
|
-
return None;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Already RTK — pass through unchanged
|
|
513
|
-
if trimmed.starts_with("rtk ") || trimmed == "rtk" {
|
|
514
|
-
return Some(trimmed.to_string());
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Special case: `head -N file` / `head --lines=N file` → `rtk read file --max-lines N`
|
|
518
|
-
// Must intercept before generic prefix replacement, which would produce `rtk read -20 file`.
|
|
519
|
-
// Only intercept when head has a flag (-N, --lines=N, -c, etc.); plain `head file` falls
|
|
520
|
-
// through to the generic rewrite below and produces `rtk read file` as expected.
|
|
521
|
-
if trimmed.starts_with("head -") {
|
|
522
|
-
return rewrite_head_numeric(trimmed);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// tail has several forms that are not compatible with generic prefix replacement.
|
|
526
|
-
// Only rewrite recognized numeric line forms; otherwise skip rewrite.
|
|
527
|
-
if trimmed.starts_with("tail ") {
|
|
528
|
-
return rewrite_tail_lines(trimmed);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Use classify_command for correct ignore/prefix handling
|
|
532
|
-
let rtk_equivalent = match classify_command(trimmed) {
|
|
533
|
-
Classification::Supported { rtk_equivalent, .. } => {
|
|
534
|
-
// Check if the base command is excluded from rewriting (#243)
|
|
535
|
-
let base = trimmed.split_whitespace().next().unwrap_or("");
|
|
536
|
-
if excluded.iter().any(|e| e == base) {
|
|
537
|
-
return None;
|
|
538
|
-
}
|
|
539
|
-
rtk_equivalent
|
|
540
|
-
}
|
|
541
|
-
_ => return None,
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
// Find the matching rule (rtk_cmd values are unique across all rules)
|
|
545
|
-
let rule = RULES.iter().find(|r| r.rtk_cmd == rtk_equivalent)?;
|
|
546
|
-
|
|
547
|
-
// Extract env prefix (sudo, env VAR=val, etc.)
|
|
548
|
-
let stripped_cow = ENV_PREFIX.replace(trimmed, "");
|
|
549
|
-
let env_prefix_len = trimmed.len() - stripped_cow.len();
|
|
550
|
-
let env_prefix = &trimmed[..env_prefix_len];
|
|
551
|
-
let cmd_clean = stripped_cow.trim();
|
|
552
|
-
|
|
553
|
-
// #345: RTK_DISABLED=1 in env prefix → skip rewrite entirely
|
|
554
|
-
if has_rtk_disabled_prefix(trimmed) {
|
|
555
|
-
return None;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// #196: gh with --json/--jq/--template produces structured output that
|
|
559
|
-
// rtk gh would corrupt — skip rewrite so the caller gets raw JSON.
|
|
560
|
-
if rule.rtk_cmd == "rtk gh" {
|
|
561
|
-
let args_lower = cmd_clean.to_lowercase();
|
|
562
|
-
if args_lower.contains("--json")
|
|
563
|
-
|| args_lower.contains("--jq")
|
|
564
|
-
|| args_lower.contains("--template")
|
|
565
|
-
{
|
|
566
|
-
return None;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Try each rewrite prefix (longest first) with word-boundary check
|
|
571
|
-
for &prefix in rule.rewrite_prefixes {
|
|
572
|
-
if let Some(rest) = strip_word_prefix(cmd_clean, prefix) {
|
|
573
|
-
let rewritten = if rest.is_empty() {
|
|
574
|
-
format!("{}{}", env_prefix, rule.rtk_cmd)
|
|
575
|
-
} else {
|
|
576
|
-
format!("{}{} {}", env_prefix, rule.rtk_cmd, rest)
|
|
577
|
-
};
|
|
578
|
-
return Some(rewritten);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
None
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/// Strip a command prefix with word-boundary check.
|
|
586
|
-
/// Returns the remainder of the command after the prefix, or `None` if no match.
|
|
587
|
-
fn strip_word_prefix<'a>(cmd: &'a str, prefix: &str) -> Option<&'a str> {
|
|
588
|
-
if cmd == prefix {
|
|
589
|
-
Some("")
|
|
590
|
-
} else if cmd.len() > prefix.len()
|
|
591
|
-
&& cmd.starts_with(prefix)
|
|
592
|
-
&& cmd.as_bytes()[prefix.len()] == b' '
|
|
593
|
-
{
|
|
594
|
-
Some(cmd[prefix.len() + 1..].trim_start())
|
|
595
|
-
} else {
|
|
596
|
-
None
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
#[cfg(test)]
|
|
601
|
-
mod tests {
|
|
602
|
-
use super::super::report::RtkStatus;
|
|
603
|
-
use super::*;
|
|
604
|
-
|
|
605
|
-
#[test]
|
|
606
|
-
fn test_classify_git_status() {
|
|
607
|
-
assert_eq!(
|
|
608
|
-
classify_command("git status"),
|
|
609
|
-
Classification::Supported {
|
|
610
|
-
rtk_equivalent: "rtk git",
|
|
611
|
-
category: "Git",
|
|
612
|
-
estimated_savings_pct: 70.0,
|
|
613
|
-
status: RtkStatus::Existing,
|
|
614
|
-
}
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
#[test]
|
|
619
|
-
fn test_classify_git_diff_cached() {
|
|
620
|
-
assert_eq!(
|
|
621
|
-
classify_command("git diff --cached"),
|
|
622
|
-
Classification::Supported {
|
|
623
|
-
rtk_equivalent: "rtk git",
|
|
624
|
-
category: "Git",
|
|
625
|
-
estimated_savings_pct: 80.0,
|
|
626
|
-
status: RtkStatus::Existing,
|
|
627
|
-
}
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
#[test]
|
|
632
|
-
fn test_classify_cargo_test_filter() {
|
|
633
|
-
assert_eq!(
|
|
634
|
-
classify_command("cargo test filter::"),
|
|
635
|
-
Classification::Supported {
|
|
636
|
-
rtk_equivalent: "rtk cargo",
|
|
637
|
-
category: "Cargo",
|
|
638
|
-
estimated_savings_pct: 90.0,
|
|
639
|
-
status: RtkStatus::Existing,
|
|
640
|
-
}
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
#[test]
|
|
645
|
-
fn test_classify_npx_tsc() {
|
|
646
|
-
assert_eq!(
|
|
647
|
-
classify_command("npx tsc --noEmit"),
|
|
648
|
-
Classification::Supported {
|
|
649
|
-
rtk_equivalent: "rtk tsc",
|
|
650
|
-
category: "Build",
|
|
651
|
-
estimated_savings_pct: 83.0,
|
|
652
|
-
status: RtkStatus::Existing,
|
|
653
|
-
}
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
#[test]
|
|
658
|
-
fn test_classify_cat_file() {
|
|
659
|
-
assert_eq!(
|
|
660
|
-
classify_command("cat src/main.rs"),
|
|
661
|
-
Classification::Supported {
|
|
662
|
-
rtk_equivalent: "rtk read",
|
|
663
|
-
category: "Files",
|
|
664
|
-
estimated_savings_pct: 60.0,
|
|
665
|
-
status: RtkStatus::Existing,
|
|
666
|
-
}
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
#[test]
|
|
671
|
-
fn test_classify_cat_redirect_not_supported() {
|
|
672
|
-
// cat > file and cat >> file are writes, not reads — should not be classified as supported
|
|
673
|
-
let write_commands = [
|
|
674
|
-
"cat > /tmp/output.txt",
|
|
675
|
-
"cat >> /tmp/output.txt",
|
|
676
|
-
"cat file.txt > output.txt",
|
|
677
|
-
"cat -n file.txt >> log.txt",
|
|
678
|
-
"head -10 README.md > output.txt",
|
|
679
|
-
"tail -f app.log > /dev/null",
|
|
680
|
-
];
|
|
681
|
-
for cmd in &write_commands {
|
|
682
|
-
match classify_command(cmd) {
|
|
683
|
-
Classification::Supported { .. } => {
|
|
684
|
-
panic!("{} should NOT be classified as Supported", cmd)
|
|
685
|
-
}
|
|
686
|
-
_ => {} // Unsupported or Ignored is fine
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
#[test]
|
|
692
|
-
fn test_classify_cd_ignored() {
|
|
693
|
-
assert_eq!(classify_command("cd /tmp"), Classification::Ignored);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
#[test]
|
|
697
|
-
fn test_classify_rtk_already() {
|
|
698
|
-
assert_eq!(classify_command("rtk git status"), Classification::Ignored);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
#[test]
|
|
702
|
-
fn test_classify_echo_ignored() {
|
|
703
|
-
assert_eq!(
|
|
704
|
-
classify_command("echo hello world"),
|
|
705
|
-
Classification::Ignored
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
#[test]
|
|
710
|
-
fn test_classify_htop_unsupported() {
|
|
711
|
-
match classify_command("htop -d 10") {
|
|
712
|
-
Classification::Unsupported { base_command } => {
|
|
713
|
-
assert_eq!(base_command, "htop");
|
|
714
|
-
}
|
|
715
|
-
other => panic!("expected Unsupported, got {:?}", other),
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
#[test]
|
|
720
|
-
fn test_classify_env_prefix_stripped() {
|
|
721
|
-
assert_eq!(
|
|
722
|
-
classify_command("GIT_SSH_COMMAND=ssh git push"),
|
|
723
|
-
Classification::Supported {
|
|
724
|
-
rtk_equivalent: "rtk git",
|
|
725
|
-
category: "Git",
|
|
726
|
-
estimated_savings_pct: 70.0,
|
|
727
|
-
status: RtkStatus::Existing,
|
|
728
|
-
}
|
|
729
|
-
);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
#[test]
|
|
733
|
-
fn test_classify_sudo_stripped() {
|
|
734
|
-
assert_eq!(
|
|
735
|
-
classify_command("sudo docker ps"),
|
|
736
|
-
Classification::Supported {
|
|
737
|
-
rtk_equivalent: "rtk docker",
|
|
738
|
-
category: "Infra",
|
|
739
|
-
estimated_savings_pct: 85.0,
|
|
740
|
-
status: RtkStatus::Existing,
|
|
741
|
-
}
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
#[test]
|
|
746
|
-
fn test_classify_cargo_check() {
|
|
747
|
-
assert_eq!(
|
|
748
|
-
classify_command("cargo check"),
|
|
749
|
-
Classification::Supported {
|
|
750
|
-
rtk_equivalent: "rtk cargo",
|
|
751
|
-
category: "Cargo",
|
|
752
|
-
estimated_savings_pct: 80.0,
|
|
753
|
-
status: RtkStatus::Existing,
|
|
754
|
-
}
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
#[test]
|
|
759
|
-
fn test_classify_cargo_check_all_targets() {
|
|
760
|
-
assert_eq!(
|
|
761
|
-
classify_command("cargo check --all-targets"),
|
|
762
|
-
Classification::Supported {
|
|
763
|
-
rtk_equivalent: "rtk cargo",
|
|
764
|
-
category: "Cargo",
|
|
765
|
-
estimated_savings_pct: 80.0,
|
|
766
|
-
status: RtkStatus::Existing,
|
|
767
|
-
}
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
#[test]
|
|
772
|
-
fn test_classify_cargo_fmt_passthrough() {
|
|
773
|
-
assert_eq!(
|
|
774
|
-
classify_command("cargo fmt"),
|
|
775
|
-
Classification::Supported {
|
|
776
|
-
rtk_equivalent: "rtk cargo",
|
|
777
|
-
category: "Cargo",
|
|
778
|
-
estimated_savings_pct: 80.0,
|
|
779
|
-
status: RtkStatus::Passthrough,
|
|
780
|
-
}
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
#[test]
|
|
785
|
-
fn test_classify_cargo_clippy_savings() {
|
|
786
|
-
assert_eq!(
|
|
787
|
-
classify_command("cargo clippy --all-targets"),
|
|
788
|
-
Classification::Supported {
|
|
789
|
-
rtk_equivalent: "rtk cargo",
|
|
790
|
-
category: "Cargo",
|
|
791
|
-
estimated_savings_pct: 80.0,
|
|
792
|
-
status: RtkStatus::Existing,
|
|
793
|
-
}
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
#[test]
|
|
798
|
-
fn test_patterns_rules_length_match() {
|
|
799
|
-
assert_eq!(
|
|
800
|
-
PATTERNS.len(),
|
|
801
|
-
RULES.len(),
|
|
802
|
-
"PATTERNS and RULES must be aligned"
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
#[test]
|
|
807
|
-
fn test_registry_covers_all_cargo_subcommands() {
|
|
808
|
-
// Verify that every CargoCommand variant (Build, Test, Clippy, Check, Fmt)
|
|
809
|
-
// except Other has a matching pattern in the registry
|
|
810
|
-
for subcmd in ["build", "test", "clippy", "check", "fmt"] {
|
|
811
|
-
let cmd = format!("cargo {subcmd}");
|
|
812
|
-
match classify_command(&cmd) {
|
|
813
|
-
Classification::Supported { .. } => {}
|
|
814
|
-
other => panic!("cargo {subcmd} should be Supported, got {other:?}"),
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
#[test]
|
|
820
|
-
fn test_registry_covers_all_git_subcommands() {
|
|
821
|
-
// Verify that every GitCommand subcommand has a matching pattern
|
|
822
|
-
for subcmd in [
|
|
823
|
-
"status", "log", "diff", "show", "add", "commit", "push", "pull", "branch", "fetch",
|
|
824
|
-
"stash", "worktree",
|
|
825
|
-
] {
|
|
826
|
-
let cmd = format!("git {subcmd}");
|
|
827
|
-
match classify_command(&cmd) {
|
|
828
|
-
Classification::Supported { .. } => {}
|
|
829
|
-
other => panic!("git {subcmd} should be Supported, got {other:?}"),
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
#[test]
|
|
835
|
-
fn test_classify_find_not_blocked_by_fi() {
|
|
836
|
-
// Regression: "fi" in IGNORED_PREFIXES used to shadow "find" commands
|
|
837
|
-
// because "find".starts_with("fi") is true. "fi" should only match exactly.
|
|
838
|
-
assert_eq!(
|
|
839
|
-
classify_command("find . -name foo"),
|
|
840
|
-
Classification::Supported {
|
|
841
|
-
rtk_equivalent: "rtk find",
|
|
842
|
-
category: "Files",
|
|
843
|
-
estimated_savings_pct: 70.0,
|
|
844
|
-
status: RtkStatus::Existing,
|
|
845
|
-
}
|
|
846
|
-
);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
#[test]
|
|
850
|
-
fn test_fi_still_ignored_exact() {
|
|
851
|
-
// Bare "fi" (shell keyword) should still be ignored
|
|
852
|
-
assert_eq!(classify_command("fi"), Classification::Ignored);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
#[test]
|
|
856
|
-
fn test_done_still_ignored_exact() {
|
|
857
|
-
// Bare "done" (shell keyword) should still be ignored
|
|
858
|
-
assert_eq!(classify_command("done"), Classification::Ignored);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
#[test]
|
|
862
|
-
fn test_split_chain_and() {
|
|
863
|
-
assert_eq!(split_command_chain("a && b"), vec!["a", "b"]);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
#[test]
|
|
867
|
-
fn test_split_chain_semicolon() {
|
|
868
|
-
assert_eq!(split_command_chain("a ; b"), vec!["a", "b"]);
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
#[test]
|
|
872
|
-
fn test_split_pipe_first_only() {
|
|
873
|
-
assert_eq!(split_command_chain("a | b"), vec!["a"]);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
#[test]
|
|
877
|
-
fn test_split_single() {
|
|
878
|
-
assert_eq!(split_command_chain("git status"), vec!["git status"]);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
#[test]
|
|
882
|
-
fn test_split_quoted_and() {
|
|
883
|
-
assert_eq!(
|
|
884
|
-
split_command_chain(r#"echo "a && b""#),
|
|
885
|
-
vec![r#"echo "a && b""#]
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
#[test]
|
|
890
|
-
fn test_split_heredoc_no_split() {
|
|
891
|
-
let cmd = "cat <<'EOF'\nhello && world\nEOF";
|
|
892
|
-
assert_eq!(split_command_chain(cmd), vec![cmd]);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
#[test]
|
|
896
|
-
fn test_classify_mypy() {
|
|
897
|
-
assert_eq!(
|
|
898
|
-
classify_command("mypy src/"),
|
|
899
|
-
Classification::Supported {
|
|
900
|
-
rtk_equivalent: "rtk mypy",
|
|
901
|
-
category: "Build",
|
|
902
|
-
estimated_savings_pct: 80.0,
|
|
903
|
-
status: RtkStatus::Existing,
|
|
904
|
-
}
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
#[test]
|
|
909
|
-
fn test_classify_python_m_mypy() {
|
|
910
|
-
assert_eq!(
|
|
911
|
-
classify_command("python3 -m mypy --strict"),
|
|
912
|
-
Classification::Supported {
|
|
913
|
-
rtk_equivalent: "rtk mypy",
|
|
914
|
-
category: "Build",
|
|
915
|
-
estimated_savings_pct: 80.0,
|
|
916
|
-
status: RtkStatus::Existing,
|
|
917
|
-
}
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// --- rewrite_command tests ---
|
|
922
|
-
|
|
923
|
-
#[test]
|
|
924
|
-
fn test_rewrite_git_status() {
|
|
925
|
-
assert_eq!(
|
|
926
|
-
rewrite_command("git status", &[]),
|
|
927
|
-
Some("rtk git status".into())
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
#[test]
|
|
932
|
-
fn test_rewrite_git_log() {
|
|
933
|
-
assert_eq!(
|
|
934
|
-
rewrite_command("git log -10", &[]),
|
|
935
|
-
Some("rtk git log -10".into())
|
|
936
|
-
);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
#[test]
|
|
940
|
-
fn test_rewrite_cargo_test() {
|
|
941
|
-
assert_eq!(
|
|
942
|
-
rewrite_command("cargo test", &[]),
|
|
943
|
-
Some("rtk cargo test".into())
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
#[test]
|
|
948
|
-
fn test_rewrite_compound_and() {
|
|
949
|
-
assert_eq!(
|
|
950
|
-
rewrite_command("git add . && cargo test", &[]),
|
|
951
|
-
Some("rtk git add . && rtk cargo test".into())
|
|
952
|
-
);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
#[test]
|
|
956
|
-
fn test_rewrite_compound_three_segments() {
|
|
957
|
-
assert_eq!(
|
|
958
|
-
rewrite_command(
|
|
959
|
-
"cargo fmt --all && cargo clippy --all-targets && cargo test",
|
|
960
|
-
&[]
|
|
961
|
-
),
|
|
962
|
-
Some("rtk cargo fmt --all && rtk cargo clippy --all-targets && rtk cargo test".into())
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
#[test]
|
|
967
|
-
fn test_rewrite_already_rtk() {
|
|
968
|
-
assert_eq!(
|
|
969
|
-
rewrite_command("rtk git status", &[]),
|
|
970
|
-
Some("rtk git status".into())
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
#[test]
|
|
975
|
-
fn test_rewrite_background_single_amp() {
|
|
976
|
-
assert_eq!(
|
|
977
|
-
rewrite_command("cargo test & git status", &[]),
|
|
978
|
-
Some("rtk cargo test & rtk git status".into())
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
#[test]
|
|
983
|
-
fn test_rewrite_background_unsupported_right() {
|
|
984
|
-
assert_eq!(
|
|
985
|
-
rewrite_command("cargo test & htop", &[]),
|
|
986
|
-
Some("rtk cargo test & htop".into())
|
|
987
|
-
);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
#[test]
|
|
991
|
-
fn test_rewrite_background_does_not_affect_double_amp() {
|
|
992
|
-
// `&&` must still work after adding `&` support
|
|
993
|
-
assert_eq!(
|
|
994
|
-
rewrite_command("cargo test && git status", &[]),
|
|
995
|
-
Some("rtk cargo test && rtk git status".into())
|
|
996
|
-
);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
#[test]
|
|
1000
|
-
fn test_rewrite_unsupported_returns_none() {
|
|
1001
|
-
assert_eq!(rewrite_command("htop", &[]), None);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
#[test]
|
|
1005
|
-
fn test_rewrite_ignored_cd() {
|
|
1006
|
-
assert_eq!(rewrite_command("cd /tmp", &[]), None);
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
#[test]
|
|
1010
|
-
fn test_rewrite_with_env_prefix() {
|
|
1011
|
-
assert_eq!(
|
|
1012
|
-
rewrite_command("GIT_SSH_COMMAND=ssh git push", &[]),
|
|
1013
|
-
Some("GIT_SSH_COMMAND=ssh rtk git push".into())
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
#[test]
|
|
1018
|
-
fn test_rewrite_npx_tsc() {
|
|
1019
|
-
assert_eq!(
|
|
1020
|
-
rewrite_command("npx tsc --noEmit", &[]),
|
|
1021
|
-
Some("rtk tsc --noEmit".into())
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
#[test]
|
|
1026
|
-
fn test_rewrite_pnpm_tsc() {
|
|
1027
|
-
assert_eq!(
|
|
1028
|
-
rewrite_command("pnpm tsc --noEmit", &[]),
|
|
1029
|
-
Some("rtk tsc --noEmit".into())
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
#[test]
|
|
1034
|
-
fn test_rewrite_cat_file() {
|
|
1035
|
-
assert_eq!(
|
|
1036
|
-
rewrite_command("cat src/main.rs", &[]),
|
|
1037
|
-
Some("rtk read src/main.rs".into())
|
|
1038
|
-
);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
#[test]
|
|
1042
|
-
fn test_rewrite_rg_pattern() {
|
|
1043
|
-
assert_eq!(
|
|
1044
|
-
rewrite_command("rg \"fn main\"", &[]),
|
|
1045
|
-
Some("rtk grep \"fn main\"".into())
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
#[test]
|
|
1050
|
-
fn test_rewrite_npx_playwright() {
|
|
1051
|
-
assert_eq!(
|
|
1052
|
-
rewrite_command("npx playwright test", &[]),
|
|
1053
|
-
Some("rtk playwright test".into())
|
|
1054
|
-
);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
#[test]
|
|
1058
|
-
fn test_rewrite_next_build() {
|
|
1059
|
-
assert_eq!(
|
|
1060
|
-
rewrite_command("next build --turbo", &[]),
|
|
1061
|
-
Some("rtk next --turbo".into())
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
#[test]
|
|
1066
|
-
fn test_rewrite_pipe_first_only() {
|
|
1067
|
-
// After a pipe, the filter command stays raw
|
|
1068
|
-
assert_eq!(
|
|
1069
|
-
rewrite_command("git log -10 | grep feat", &[]),
|
|
1070
|
-
Some("rtk git log -10 | grep feat".into())
|
|
1071
|
-
);
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
#[test]
|
|
1075
|
-
fn test_rewrite_heredoc_returns_none() {
|
|
1076
|
-
assert_eq!(rewrite_command("cat <<'EOF'\nfoo\nEOF", &[]), None);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
#[test]
|
|
1080
|
-
fn test_rewrite_empty_returns_none() {
|
|
1081
|
-
assert_eq!(rewrite_command("", &[]), None);
|
|
1082
|
-
assert_eq!(rewrite_command(" ", &[]), None);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
#[test]
|
|
1086
|
-
fn test_rewrite_mixed_compound_partial() {
|
|
1087
|
-
// First segment already RTK, second gets rewritten
|
|
1088
|
-
assert_eq!(
|
|
1089
|
-
rewrite_command("rtk git add . && cargo test", &[]),
|
|
1090
|
-
Some("rtk git add . && rtk cargo test".into())
|
|
1091
|
-
);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// --- #345: RTK_DISABLED ---
|
|
1095
|
-
|
|
1096
|
-
#[test]
|
|
1097
|
-
fn test_rewrite_rtk_disabled_curl() {
|
|
1098
|
-
assert_eq!(
|
|
1099
|
-
rewrite_command("RTK_DISABLED=1 curl https://example.com", &[]),
|
|
1100
|
-
None
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
#[test]
|
|
1105
|
-
fn test_rewrite_rtk_disabled_git_status() {
|
|
1106
|
-
assert_eq!(rewrite_command("RTK_DISABLED=1 git status", &[]), None);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
#[test]
|
|
1110
|
-
fn test_rewrite_rtk_disabled_multi_env() {
|
|
1111
|
-
assert_eq!(
|
|
1112
|
-
rewrite_command("FOO=1 RTK_DISABLED=1 git status", &[]),
|
|
1113
|
-
None
|
|
1114
|
-
);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
#[test]
|
|
1118
|
-
fn test_rewrite_non_rtk_disabled_env_still_rewrites() {
|
|
1119
|
-
assert_eq!(
|
|
1120
|
-
rewrite_command("SOME_VAR=1 git status", &[]),
|
|
1121
|
-
Some("SOME_VAR=1 rtk git status".into())
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
// --- #346: 2>&1 and &> redirect detection ---
|
|
1126
|
-
|
|
1127
|
-
#[test]
|
|
1128
|
-
fn test_rewrite_redirect_2_gt_amp_1_with_pipe() {
|
|
1129
|
-
assert_eq!(
|
|
1130
|
-
rewrite_command("cargo test 2>&1 | head", &[]),
|
|
1131
|
-
Some("rtk cargo test 2>&1 | head".into())
|
|
1132
|
-
);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
#[test]
|
|
1136
|
-
fn test_rewrite_redirect_2_gt_amp_1_trailing() {
|
|
1137
|
-
assert_eq!(
|
|
1138
|
-
rewrite_command("cargo test 2>&1", &[]),
|
|
1139
|
-
Some("rtk cargo test 2>&1".into())
|
|
1140
|
-
);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
#[test]
|
|
1144
|
-
fn test_rewrite_redirect_plain_2_devnull() {
|
|
1145
|
-
// 2>/dev/null has no `&`, never broken — non-regression
|
|
1146
|
-
assert_eq!(
|
|
1147
|
-
rewrite_command("git status 2>/dev/null", &[]),
|
|
1148
|
-
Some("rtk git status 2>/dev/null".into())
|
|
1149
|
-
);
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
#[test]
|
|
1153
|
-
fn test_rewrite_redirect_2_gt_amp_1_with_and() {
|
|
1154
|
-
assert_eq!(
|
|
1155
|
-
rewrite_command("cargo test 2>&1 && echo done", &[]),
|
|
1156
|
-
Some("rtk cargo test 2>&1 && echo done".into())
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
#[test]
|
|
1161
|
-
fn test_rewrite_redirect_amp_gt_devnull() {
|
|
1162
|
-
assert_eq!(
|
|
1163
|
-
rewrite_command("cargo test &>/dev/null", &[]),
|
|
1164
|
-
Some("rtk cargo test &>/dev/null".into())
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
#[test]
|
|
1169
|
-
fn test_rewrite_background_amp_non_regression() {
|
|
1170
|
-
// background `&` must still work after redirect fix
|
|
1171
|
-
assert_eq!(
|
|
1172
|
-
rewrite_command("cargo test & git status", &[]),
|
|
1173
|
-
Some("rtk cargo test & rtk git status".into())
|
|
1174
|
-
);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// --- P0.2: head -N rewrite ---
|
|
1178
|
-
|
|
1179
|
-
#[test]
|
|
1180
|
-
fn test_rewrite_head_numeric_flag() {
|
|
1181
|
-
// head -20 file → rtk read file --max-lines 20 (not rtk read -20 file)
|
|
1182
|
-
assert_eq!(
|
|
1183
|
-
rewrite_command("head -20 src/main.rs", &[]),
|
|
1184
|
-
Some("rtk read src/main.rs --max-lines 20".into())
|
|
1185
|
-
);
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
#[test]
|
|
1189
|
-
fn test_rewrite_head_lines_long_flag() {
|
|
1190
|
-
assert_eq!(
|
|
1191
|
-
rewrite_command("head --lines=50 src/lib.rs", &[]),
|
|
1192
|
-
Some("rtk read src/lib.rs --max-lines 50".into())
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
#[test]
|
|
1197
|
-
fn test_rewrite_head_no_flag_still_rewrites() {
|
|
1198
|
-
// plain `head file` → `rtk read file` (no numeric flag)
|
|
1199
|
-
assert_eq!(
|
|
1200
|
-
rewrite_command("head src/main.rs", &[]),
|
|
1201
|
-
Some("rtk read src/main.rs".into())
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
#[test]
|
|
1206
|
-
fn test_rewrite_head_other_flag_skipped() {
|
|
1207
|
-
// head -c 100 file: unsupported flag, skip rewriting
|
|
1208
|
-
assert_eq!(rewrite_command("head -c 100 src/main.rs", &[]), None);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
#[test]
|
|
1212
|
-
fn test_rewrite_tail_numeric_flag() {
|
|
1213
|
-
assert_eq!(
|
|
1214
|
-
rewrite_command("tail -20 src/main.rs", &[]),
|
|
1215
|
-
Some("rtk read src/main.rs --tail-lines 20".into())
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
#[test]
|
|
1220
|
-
fn test_rewrite_tail_n_space_flag() {
|
|
1221
|
-
assert_eq!(
|
|
1222
|
-
rewrite_command("tail -n 12 src/lib.rs", &[]),
|
|
1223
|
-
Some("rtk read src/lib.rs --tail-lines 12".into())
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
#[test]
|
|
1228
|
-
fn test_rewrite_tail_lines_long_flag() {
|
|
1229
|
-
assert_eq!(
|
|
1230
|
-
rewrite_command("tail --lines=7 src/lib.rs", &[]),
|
|
1231
|
-
Some("rtk read src/lib.rs --tail-lines 7".into())
|
|
1232
|
-
);
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
#[test]
|
|
1236
|
-
fn test_rewrite_tail_lines_space_flag() {
|
|
1237
|
-
assert_eq!(
|
|
1238
|
-
rewrite_command("tail --lines 7 src/lib.rs", &[]),
|
|
1239
|
-
Some("rtk read src/lib.rs --tail-lines 7".into())
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
#[test]
|
|
1244
|
-
fn test_rewrite_tail_other_flag_skipped() {
|
|
1245
|
-
assert_eq!(rewrite_command("tail -c 100 src/main.rs", &[]), None);
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
#[test]
|
|
1249
|
-
fn test_rewrite_tail_plain_file_skipped() {
|
|
1250
|
-
assert_eq!(rewrite_command("tail src/main.rs", &[]), None);
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// --- New registry entries ---
|
|
1254
|
-
|
|
1255
|
-
#[test]
|
|
1256
|
-
fn test_classify_gh_release() {
|
|
1257
|
-
assert!(matches!(
|
|
1258
|
-
classify_command("gh release list"),
|
|
1259
|
-
Classification::Supported {
|
|
1260
|
-
rtk_equivalent: "rtk gh",
|
|
1261
|
-
..
|
|
1262
|
-
}
|
|
1263
|
-
));
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
#[test]
|
|
1267
|
-
fn test_classify_cargo_install() {
|
|
1268
|
-
assert!(matches!(
|
|
1269
|
-
classify_command("cargo install rtk"),
|
|
1270
|
-
Classification::Supported {
|
|
1271
|
-
rtk_equivalent: "rtk cargo",
|
|
1272
|
-
..
|
|
1273
|
-
}
|
|
1274
|
-
));
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
#[test]
|
|
1278
|
-
fn test_classify_docker_run() {
|
|
1279
|
-
assert!(matches!(
|
|
1280
|
-
classify_command("docker run --rm ubuntu bash"),
|
|
1281
|
-
Classification::Supported {
|
|
1282
|
-
rtk_equivalent: "rtk docker",
|
|
1283
|
-
..
|
|
1284
|
-
}
|
|
1285
|
-
));
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
#[test]
|
|
1289
|
-
fn test_classify_docker_exec() {
|
|
1290
|
-
assert!(matches!(
|
|
1291
|
-
classify_command("docker exec -it mycontainer bash"),
|
|
1292
|
-
Classification::Supported {
|
|
1293
|
-
rtk_equivalent: "rtk docker",
|
|
1294
|
-
..
|
|
1295
|
-
}
|
|
1296
|
-
));
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
#[test]
|
|
1300
|
-
fn test_classify_docker_build() {
|
|
1301
|
-
assert!(matches!(
|
|
1302
|
-
classify_command("docker build -t myimage ."),
|
|
1303
|
-
Classification::Supported {
|
|
1304
|
-
rtk_equivalent: "rtk docker",
|
|
1305
|
-
..
|
|
1306
|
-
}
|
|
1307
|
-
));
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
#[test]
|
|
1311
|
-
fn test_classify_kubectl_describe() {
|
|
1312
|
-
assert!(matches!(
|
|
1313
|
-
classify_command("kubectl describe pod mypod"),
|
|
1314
|
-
Classification::Supported {
|
|
1315
|
-
rtk_equivalent: "rtk kubectl",
|
|
1316
|
-
..
|
|
1317
|
-
}
|
|
1318
|
-
));
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
#[test]
|
|
1322
|
-
fn test_classify_kubectl_apply() {
|
|
1323
|
-
assert!(matches!(
|
|
1324
|
-
classify_command("kubectl apply -f deploy.yaml"),
|
|
1325
|
-
Classification::Supported {
|
|
1326
|
-
rtk_equivalent: "rtk kubectl",
|
|
1327
|
-
..
|
|
1328
|
-
}
|
|
1329
|
-
));
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
#[test]
|
|
1333
|
-
fn test_classify_tree() {
|
|
1334
|
-
assert!(matches!(
|
|
1335
|
-
classify_command("tree src/"),
|
|
1336
|
-
Classification::Supported {
|
|
1337
|
-
rtk_equivalent: "rtk tree",
|
|
1338
|
-
..
|
|
1339
|
-
}
|
|
1340
|
-
));
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
#[test]
|
|
1344
|
-
fn test_classify_diff() {
|
|
1345
|
-
assert!(matches!(
|
|
1346
|
-
classify_command("diff file1.txt file2.txt"),
|
|
1347
|
-
Classification::Supported {
|
|
1348
|
-
rtk_equivalent: "rtk diff",
|
|
1349
|
-
..
|
|
1350
|
-
}
|
|
1351
|
-
));
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
#[test]
|
|
1355
|
-
fn test_rewrite_tree() {
|
|
1356
|
-
assert_eq!(
|
|
1357
|
-
rewrite_command("tree src/", &[]),
|
|
1358
|
-
Some("rtk tree src/".into())
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
#[test]
|
|
1363
|
-
fn test_rewrite_diff() {
|
|
1364
|
-
assert_eq!(
|
|
1365
|
-
rewrite_command("diff file1.txt file2.txt", &[]),
|
|
1366
|
-
Some("rtk diff file1.txt file2.txt".into())
|
|
1367
|
-
);
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
#[test]
|
|
1371
|
-
fn test_rewrite_gh_release() {
|
|
1372
|
-
assert_eq!(
|
|
1373
|
-
rewrite_command("gh release list", &[]),
|
|
1374
|
-
Some("rtk gh release list".into())
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
#[test]
|
|
1379
|
-
fn test_rewrite_cargo_install() {
|
|
1380
|
-
assert_eq!(
|
|
1381
|
-
rewrite_command("cargo install rtk", &[]),
|
|
1382
|
-
Some("rtk cargo install rtk".into())
|
|
1383
|
-
);
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
#[test]
|
|
1387
|
-
fn test_rewrite_kubectl_describe() {
|
|
1388
|
-
assert_eq!(
|
|
1389
|
-
rewrite_command("kubectl describe pod mypod", &[]),
|
|
1390
|
-
Some("rtk kubectl describe pod mypod".into())
|
|
1391
|
-
);
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
#[test]
|
|
1395
|
-
fn test_rewrite_docker_run() {
|
|
1396
|
-
assert_eq!(
|
|
1397
|
-
rewrite_command("docker run --rm ubuntu bash", &[]),
|
|
1398
|
-
Some("rtk docker run --rm ubuntu bash".into())
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
// --- #336: docker compose supported subcommands rewritten, unsupported skipped ---
|
|
1403
|
-
|
|
1404
|
-
#[test]
|
|
1405
|
-
fn test_rewrite_docker_compose_ps() {
|
|
1406
|
-
assert_eq!(
|
|
1407
|
-
rewrite_command("docker compose ps", &[]),
|
|
1408
|
-
Some("rtk docker compose ps".into())
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
#[test]
|
|
1413
|
-
fn test_rewrite_docker_compose_logs() {
|
|
1414
|
-
assert_eq!(
|
|
1415
|
-
rewrite_command("docker compose logs web", &[]),
|
|
1416
|
-
Some("rtk docker compose logs web".into())
|
|
1417
|
-
);
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
#[test]
|
|
1421
|
-
fn test_rewrite_docker_compose_build() {
|
|
1422
|
-
assert_eq!(
|
|
1423
|
-
rewrite_command("docker compose build", &[]),
|
|
1424
|
-
Some("rtk docker compose build".into())
|
|
1425
|
-
);
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
#[test]
|
|
1429
|
-
fn test_rewrite_docker_compose_up_skipped() {
|
|
1430
|
-
assert_eq!(rewrite_command("docker compose up -d", &[]), None);
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
#[test]
|
|
1434
|
-
fn test_rewrite_docker_compose_down_skipped() {
|
|
1435
|
-
assert_eq!(rewrite_command("docker compose down", &[]), None);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
#[test]
|
|
1439
|
-
fn test_rewrite_docker_compose_config_skipped() {
|
|
1440
|
-
assert_eq!(
|
|
1441
|
-
rewrite_command("docker compose -f foo.yaml config --services", &[]),
|
|
1442
|
-
None
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// --- AWS / psql (PR #216) ---
|
|
1447
|
-
|
|
1448
|
-
#[test]
|
|
1449
|
-
fn test_classify_aws() {
|
|
1450
|
-
assert!(matches!(
|
|
1451
|
-
classify_command("aws s3 ls"),
|
|
1452
|
-
Classification::Supported {
|
|
1453
|
-
rtk_equivalent: "rtk aws",
|
|
1454
|
-
..
|
|
1455
|
-
}
|
|
1456
|
-
));
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
#[test]
|
|
1460
|
-
fn test_classify_aws_ec2() {
|
|
1461
|
-
assert!(matches!(
|
|
1462
|
-
classify_command("aws ec2 describe-instances"),
|
|
1463
|
-
Classification::Supported {
|
|
1464
|
-
rtk_equivalent: "rtk aws",
|
|
1465
|
-
..
|
|
1466
|
-
}
|
|
1467
|
-
));
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
#[test]
|
|
1471
|
-
fn test_classify_psql() {
|
|
1472
|
-
assert!(matches!(
|
|
1473
|
-
classify_command("psql -U postgres"),
|
|
1474
|
-
Classification::Supported {
|
|
1475
|
-
rtk_equivalent: "rtk psql",
|
|
1476
|
-
..
|
|
1477
|
-
}
|
|
1478
|
-
));
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
#[test]
|
|
1482
|
-
fn test_classify_psql_url() {
|
|
1483
|
-
assert!(matches!(
|
|
1484
|
-
classify_command("psql postgres://localhost/mydb"),
|
|
1485
|
-
Classification::Supported {
|
|
1486
|
-
rtk_equivalent: "rtk psql",
|
|
1487
|
-
..
|
|
1488
|
-
}
|
|
1489
|
-
));
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
#[test]
|
|
1493
|
-
fn test_rewrite_aws() {
|
|
1494
|
-
assert_eq!(
|
|
1495
|
-
rewrite_command("aws s3 ls", &[]),
|
|
1496
|
-
Some("rtk aws s3 ls".into())
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
#[test]
|
|
1501
|
-
fn test_rewrite_aws_ec2() {
|
|
1502
|
-
assert_eq!(
|
|
1503
|
-
rewrite_command("aws ec2 describe-instances --region us-east-1", &[]),
|
|
1504
|
-
Some("rtk aws ec2 describe-instances --region us-east-1".into())
|
|
1505
|
-
);
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
#[test]
|
|
1509
|
-
fn test_rewrite_psql() {
|
|
1510
|
-
assert_eq!(
|
|
1511
|
-
rewrite_command("psql -U postgres -d mydb", &[]),
|
|
1512
|
-
Some("rtk psql -U postgres -d mydb".into())
|
|
1513
|
-
);
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// --- Python tooling ---
|
|
1517
|
-
|
|
1518
|
-
#[test]
|
|
1519
|
-
fn test_classify_ruff_check() {
|
|
1520
|
-
assert!(matches!(
|
|
1521
|
-
classify_command("ruff check ."),
|
|
1522
|
-
Classification::Supported {
|
|
1523
|
-
rtk_equivalent: "rtk ruff",
|
|
1524
|
-
..
|
|
1525
|
-
}
|
|
1526
|
-
));
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
#[test]
|
|
1530
|
-
fn test_classify_ruff_format() {
|
|
1531
|
-
assert!(matches!(
|
|
1532
|
-
classify_command("ruff format src/"),
|
|
1533
|
-
Classification::Supported {
|
|
1534
|
-
rtk_equivalent: "rtk ruff",
|
|
1535
|
-
..
|
|
1536
|
-
}
|
|
1537
|
-
));
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
#[test]
|
|
1541
|
-
fn test_classify_pytest() {
|
|
1542
|
-
assert!(matches!(
|
|
1543
|
-
classify_command("pytest tests/"),
|
|
1544
|
-
Classification::Supported {
|
|
1545
|
-
rtk_equivalent: "rtk pytest",
|
|
1546
|
-
..
|
|
1547
|
-
}
|
|
1548
|
-
));
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
#[test]
|
|
1552
|
-
fn test_classify_python_m_pytest() {
|
|
1553
|
-
assert!(matches!(
|
|
1554
|
-
classify_command("python -m pytest tests/"),
|
|
1555
|
-
Classification::Supported {
|
|
1556
|
-
rtk_equivalent: "rtk pytest",
|
|
1557
|
-
..
|
|
1558
|
-
}
|
|
1559
|
-
));
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
#[test]
|
|
1563
|
-
fn test_classify_pip_list() {
|
|
1564
|
-
assert!(matches!(
|
|
1565
|
-
classify_command("pip list"),
|
|
1566
|
-
Classification::Supported {
|
|
1567
|
-
rtk_equivalent: "rtk pip",
|
|
1568
|
-
..
|
|
1569
|
-
}
|
|
1570
|
-
));
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
#[test]
|
|
1574
|
-
fn test_classify_uv_pip_list() {
|
|
1575
|
-
assert!(matches!(
|
|
1576
|
-
classify_command("uv pip list"),
|
|
1577
|
-
Classification::Supported {
|
|
1578
|
-
rtk_equivalent: "rtk pip",
|
|
1579
|
-
..
|
|
1580
|
-
}
|
|
1581
|
-
));
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
#[test]
|
|
1585
|
-
fn test_rewrite_ruff_check() {
|
|
1586
|
-
assert_eq!(
|
|
1587
|
-
rewrite_command("ruff check .", &[]),
|
|
1588
|
-
Some("rtk ruff check .".into())
|
|
1589
|
-
);
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
#[test]
|
|
1593
|
-
fn test_rewrite_ruff_format() {
|
|
1594
|
-
assert_eq!(
|
|
1595
|
-
rewrite_command("ruff format src/", &[]),
|
|
1596
|
-
Some("rtk ruff format src/".into())
|
|
1597
|
-
);
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
#[test]
|
|
1601
|
-
fn test_rewrite_pytest() {
|
|
1602
|
-
assert_eq!(
|
|
1603
|
-
rewrite_command("pytest tests/", &[]),
|
|
1604
|
-
Some("rtk pytest tests/".into())
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
#[test]
|
|
1609
|
-
fn test_rewrite_python_m_pytest() {
|
|
1610
|
-
assert_eq!(
|
|
1611
|
-
rewrite_command("python -m pytest -x tests/", &[]),
|
|
1612
|
-
Some("rtk pytest -x tests/".into())
|
|
1613
|
-
);
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
#[test]
|
|
1617
|
-
fn test_rewrite_pip_list() {
|
|
1618
|
-
assert_eq!(
|
|
1619
|
-
rewrite_command("pip list", &[]),
|
|
1620
|
-
Some("rtk pip list".into())
|
|
1621
|
-
);
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
#[test]
|
|
1625
|
-
fn test_rewrite_pip_outdated() {
|
|
1626
|
-
assert_eq!(
|
|
1627
|
-
rewrite_command("pip outdated", &[]),
|
|
1628
|
-
Some("rtk pip outdated".into())
|
|
1629
|
-
);
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
#[test]
|
|
1633
|
-
fn test_rewrite_uv_pip_list() {
|
|
1634
|
-
assert_eq!(
|
|
1635
|
-
rewrite_command("uv pip list", &[]),
|
|
1636
|
-
Some("rtk pip list".into())
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
// --- Go tooling ---
|
|
1641
|
-
|
|
1642
|
-
#[test]
|
|
1643
|
-
fn test_classify_go_test() {
|
|
1644
|
-
assert!(matches!(
|
|
1645
|
-
classify_command("go test ./..."),
|
|
1646
|
-
Classification::Supported {
|
|
1647
|
-
rtk_equivalent: "rtk go",
|
|
1648
|
-
..
|
|
1649
|
-
}
|
|
1650
|
-
));
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
#[test]
|
|
1654
|
-
fn test_classify_go_build() {
|
|
1655
|
-
assert!(matches!(
|
|
1656
|
-
classify_command("go build ./..."),
|
|
1657
|
-
Classification::Supported {
|
|
1658
|
-
rtk_equivalent: "rtk go",
|
|
1659
|
-
..
|
|
1660
|
-
}
|
|
1661
|
-
));
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
#[test]
|
|
1665
|
-
fn test_classify_go_vet() {
|
|
1666
|
-
assert!(matches!(
|
|
1667
|
-
classify_command("go vet ./..."),
|
|
1668
|
-
Classification::Supported {
|
|
1669
|
-
rtk_equivalent: "rtk go",
|
|
1670
|
-
..
|
|
1671
|
-
}
|
|
1672
|
-
));
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
#[test]
|
|
1676
|
-
fn test_classify_golangci_lint() {
|
|
1677
|
-
assert!(matches!(
|
|
1678
|
-
classify_command("golangci-lint run"),
|
|
1679
|
-
Classification::Supported {
|
|
1680
|
-
rtk_equivalent: "rtk golangci-lint",
|
|
1681
|
-
..
|
|
1682
|
-
}
|
|
1683
|
-
));
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
#[test]
|
|
1687
|
-
fn test_rewrite_go_test() {
|
|
1688
|
-
assert_eq!(
|
|
1689
|
-
rewrite_command("go test ./...", &[]),
|
|
1690
|
-
Some("rtk go test ./...".into())
|
|
1691
|
-
);
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
#[test]
|
|
1695
|
-
fn test_rewrite_go_build() {
|
|
1696
|
-
assert_eq!(
|
|
1697
|
-
rewrite_command("go build ./...", &[]),
|
|
1698
|
-
Some("rtk go build ./...".into())
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
#[test]
|
|
1703
|
-
fn test_rewrite_go_vet() {
|
|
1704
|
-
assert_eq!(
|
|
1705
|
-
rewrite_command("go vet ./...", &[]),
|
|
1706
|
-
Some("rtk go vet ./...".into())
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
#[test]
|
|
1711
|
-
fn test_rewrite_golangci_lint() {
|
|
1712
|
-
assert_eq!(
|
|
1713
|
-
rewrite_command("golangci-lint run ./...", &[]),
|
|
1714
|
-
Some("rtk golangci-lint run ./...".into())
|
|
1715
|
-
);
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
// --- JS/TS tooling ---
|
|
1719
|
-
|
|
1720
|
-
#[test]
|
|
1721
|
-
fn test_classify_vitest() {
|
|
1722
|
-
assert!(matches!(
|
|
1723
|
-
classify_command("vitest run"),
|
|
1724
|
-
Classification::Supported {
|
|
1725
|
-
rtk_equivalent: "rtk vitest",
|
|
1726
|
-
..
|
|
1727
|
-
}
|
|
1728
|
-
));
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
#[test]
|
|
1732
|
-
fn test_rewrite_vitest() {
|
|
1733
|
-
assert_eq!(
|
|
1734
|
-
rewrite_command("vitest run", &[]),
|
|
1735
|
-
Some("rtk vitest run".into())
|
|
1736
|
-
);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
#[test]
|
|
1740
|
-
fn test_rewrite_pnpm_vitest() {
|
|
1741
|
-
assert_eq!(
|
|
1742
|
-
rewrite_command("pnpm vitest run", &[]),
|
|
1743
|
-
Some("rtk vitest run".into())
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
#[test]
|
|
1748
|
-
fn test_classify_prisma() {
|
|
1749
|
-
assert!(matches!(
|
|
1750
|
-
classify_command("npx prisma migrate dev"),
|
|
1751
|
-
Classification::Supported {
|
|
1752
|
-
rtk_equivalent: "rtk prisma",
|
|
1753
|
-
..
|
|
1754
|
-
}
|
|
1755
|
-
));
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
#[test]
|
|
1759
|
-
fn test_rewrite_prisma() {
|
|
1760
|
-
assert_eq!(
|
|
1761
|
-
rewrite_command("npx prisma migrate dev", &[]),
|
|
1762
|
-
Some("rtk prisma migrate dev".into())
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
#[test]
|
|
1767
|
-
fn test_rewrite_prettier() {
|
|
1768
|
-
assert_eq!(
|
|
1769
|
-
rewrite_command("npx prettier --check src/", &[]),
|
|
1770
|
-
Some("rtk prettier --check src/".into())
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
#[test]
|
|
1775
|
-
fn test_rewrite_pnpm_list() {
|
|
1776
|
-
assert_eq!(
|
|
1777
|
-
rewrite_command("pnpm list", &[]),
|
|
1778
|
-
Some("rtk pnpm list".into())
|
|
1779
|
-
);
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
// --- Compound operator edge cases ---
|
|
1783
|
-
|
|
1784
|
-
#[test]
|
|
1785
|
-
fn test_rewrite_compound_or() {
|
|
1786
|
-
// `||` fallback: left rewritten, right rewritten
|
|
1787
|
-
assert_eq!(
|
|
1788
|
-
rewrite_command("cargo test || cargo build", &[]),
|
|
1789
|
-
Some("rtk cargo test || rtk cargo build".into())
|
|
1790
|
-
);
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
#[test]
|
|
1794
|
-
fn test_rewrite_compound_semicolon() {
|
|
1795
|
-
assert_eq!(
|
|
1796
|
-
rewrite_command("git status; cargo test", &[]),
|
|
1797
|
-
Some("rtk git status; rtk cargo test".into())
|
|
1798
|
-
);
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
#[test]
|
|
1802
|
-
fn test_rewrite_compound_pipe_raw_filter() {
|
|
1803
|
-
// Pipe: rewrite first segment only, pass through rest unchanged
|
|
1804
|
-
assert_eq!(
|
|
1805
|
-
rewrite_command("cargo test | grep FAILED", &[]),
|
|
1806
|
-
Some("rtk cargo test | grep FAILED".into())
|
|
1807
|
-
);
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
#[test]
|
|
1811
|
-
fn test_rewrite_compound_pipe_git_grep() {
|
|
1812
|
-
assert_eq!(
|
|
1813
|
-
rewrite_command("git log -10 | grep feat", &[]),
|
|
1814
|
-
Some("rtk git log -10 | grep feat".into())
|
|
1815
|
-
);
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
#[test]
|
|
1819
|
-
fn test_rewrite_compound_four_segments() {
|
|
1820
|
-
assert_eq!(
|
|
1821
|
-
rewrite_command(
|
|
1822
|
-
"cargo fmt --all && cargo clippy && cargo test && git status",
|
|
1823
|
-
&[]
|
|
1824
|
-
),
|
|
1825
|
-
Some(
|
|
1826
|
-
"rtk cargo fmt --all && rtk cargo clippy && rtk cargo test && rtk git status"
|
|
1827
|
-
.into()
|
|
1828
|
-
)
|
|
1829
|
-
);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
#[test]
|
|
1833
|
-
fn test_rewrite_compound_mixed_supported_unsupported() {
|
|
1834
|
-
// unsupported segments stay raw
|
|
1835
|
-
assert_eq!(
|
|
1836
|
-
rewrite_command("cargo test && htop", &[]),
|
|
1837
|
-
Some("rtk cargo test && htop".into())
|
|
1838
|
-
);
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
#[test]
|
|
1842
|
-
fn test_rewrite_compound_all_unsupported_returns_none() {
|
|
1843
|
-
// No rewrite at all: returns None
|
|
1844
|
-
assert_eq!(rewrite_command("htop && top", &[]), None);
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
// --- sudo / env prefix + rewrite ---
|
|
1848
|
-
|
|
1849
|
-
#[test]
|
|
1850
|
-
fn test_rewrite_sudo_docker() {
|
|
1851
|
-
assert_eq!(
|
|
1852
|
-
rewrite_command("sudo docker ps", &[]),
|
|
1853
|
-
Some("sudo rtk docker ps".into())
|
|
1854
|
-
);
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
#[test]
|
|
1858
|
-
fn test_rewrite_env_var_prefix() {
|
|
1859
|
-
assert_eq!(
|
|
1860
|
-
rewrite_command("GIT_SSH_COMMAND=ssh git push origin main", &[]),
|
|
1861
|
-
Some("GIT_SSH_COMMAND=ssh rtk git push origin main".into())
|
|
1862
|
-
);
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
// --- find with native flags ---
|
|
1866
|
-
|
|
1867
|
-
#[test]
|
|
1868
|
-
fn test_rewrite_find_with_flags() {
|
|
1869
|
-
assert_eq!(
|
|
1870
|
-
rewrite_command("find . -name '*.rs' -type f", &[]),
|
|
1871
|
-
Some("rtk find . -name '*.rs' -type f".into())
|
|
1872
|
-
);
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
// --- Ensure PATTERNS and RULES stay aligned after modifications ---
|
|
1876
|
-
|
|
1877
|
-
#[test]
|
|
1878
|
-
fn test_patterns_rules_aligned_after_aws_psql() {
|
|
1879
|
-
// If this fails, someone added a PATTERN without a matching RULE (or vice versa)
|
|
1880
|
-
assert_eq!(
|
|
1881
|
-
PATTERNS.len(),
|
|
1882
|
-
RULES.len(),
|
|
1883
|
-
"PATTERNS[{}] != RULES[{}] — they must stay 1:1",
|
|
1884
|
-
PATTERNS.len(),
|
|
1885
|
-
RULES.len()
|
|
1886
|
-
);
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
// --- All RULES have non-empty rtk_cmd and at least one rewrite_prefix ---
|
|
1890
|
-
|
|
1891
|
-
#[test]
|
|
1892
|
-
fn test_all_rules_have_valid_rtk_cmd() {
|
|
1893
|
-
for rule in RULES {
|
|
1894
|
-
assert!(!rule.rtk_cmd.is_empty(), "Rule with empty rtk_cmd found");
|
|
1895
|
-
assert!(
|
|
1896
|
-
rule.rtk_cmd.starts_with("rtk "),
|
|
1897
|
-
"rtk_cmd '{}' must start with 'rtk '",
|
|
1898
|
-
rule.rtk_cmd
|
|
1899
|
-
);
|
|
1900
|
-
assert!(
|
|
1901
|
-
!rule.rewrite_prefixes.is_empty(),
|
|
1902
|
-
"Rule '{}' has no rewrite_prefixes",
|
|
1903
|
-
rule.rtk_cmd
|
|
1904
|
-
);
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
|
|
1908
|
-
// --- exclude_commands (#243) ---
|
|
1909
|
-
|
|
1910
|
-
#[test]
|
|
1911
|
-
fn test_rewrite_excludes_curl() {
|
|
1912
|
-
let excluded = vec!["curl".to_string()];
|
|
1913
|
-
assert_eq!(
|
|
1914
|
-
rewrite_command("curl https://api.example.com/health", &excluded),
|
|
1915
|
-
None
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
#[test]
|
|
1920
|
-
fn test_rewrite_exclude_does_not_affect_other_commands() {
|
|
1921
|
-
let excluded = vec!["curl".to_string()];
|
|
1922
|
-
assert_eq!(
|
|
1923
|
-
rewrite_command("git status", &excluded),
|
|
1924
|
-
Some("rtk git status".into())
|
|
1925
|
-
);
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
#[test]
|
|
1929
|
-
fn test_rewrite_empty_excludes_rewrites_curl() {
|
|
1930
|
-
let excluded: Vec<String> = vec![];
|
|
1931
|
-
assert!(rewrite_command("curl https://api.example.com", &excluded).is_some());
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
#[test]
|
|
1935
|
-
fn test_rewrite_compound_partial_exclude() {
|
|
1936
|
-
// curl excluded but git still rewrites
|
|
1937
|
-
let excluded = vec!["curl".to_string()];
|
|
1938
|
-
assert_eq!(
|
|
1939
|
-
rewrite_command("git status && curl https://api.example.com", &excluded),
|
|
1940
|
-
Some("rtk git status && curl https://api.example.com".into())
|
|
1941
|
-
);
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
// --- Every PATTERN compiles to a valid Regex ---
|
|
1945
|
-
|
|
1946
|
-
#[test]
|
|
1947
|
-
fn test_all_patterns_are_valid_regex() {
|
|
1948
|
-
use regex::Regex;
|
|
1949
|
-
for (i, pattern) in PATTERNS.iter().enumerate() {
|
|
1950
|
-
assert!(
|
|
1951
|
-
Regex::new(pattern).is_ok(),
|
|
1952
|
-
"PATTERNS[{i}] = '{pattern}' is not a valid regex"
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
// --- #196: gh --json/--jq/--template passthrough ---
|
|
1958
|
-
|
|
1959
|
-
#[test]
|
|
1960
|
-
fn test_rewrite_gh_json_skipped() {
|
|
1961
|
-
assert_eq!(rewrite_command("gh pr list --json number,title", &[]), None);
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
#[test]
|
|
1965
|
-
fn test_rewrite_gh_jq_skipped() {
|
|
1966
|
-
assert_eq!(
|
|
1967
|
-
rewrite_command("gh pr list --json number --jq '.[].number'", &[]),
|
|
1968
|
-
None
|
|
1969
|
-
);
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
#[test]
|
|
1973
|
-
fn test_rewrite_gh_template_skipped() {
|
|
1974
|
-
assert_eq!(
|
|
1975
|
-
rewrite_command("gh pr view 42 --template '{{.title}}'", &[]),
|
|
1976
|
-
None
|
|
1977
|
-
);
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
#[test]
|
|
1981
|
-
fn test_rewrite_gh_api_json_skipped() {
|
|
1982
|
-
assert_eq!(
|
|
1983
|
-
rewrite_command("gh api repos/owner/repo --jq '.name'", &[]),
|
|
1984
|
-
None
|
|
1985
|
-
);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
#[test]
|
|
1989
|
-
fn test_rewrite_gh_without_json_still_works() {
|
|
1990
|
-
assert_eq!(
|
|
1991
|
-
rewrite_command("gh pr list", &[]),
|
|
1992
|
-
Some("rtk gh pr list".into())
|
|
1993
|
-
);
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// --- #508: RTK_DISABLED detection helpers ---
|
|
1997
|
-
|
|
1998
|
-
#[test]
|
|
1999
|
-
fn test_has_rtk_disabled_prefix() {
|
|
2000
|
-
assert!(has_rtk_disabled_prefix("RTK_DISABLED=1 git status"));
|
|
2001
|
-
assert!(has_rtk_disabled_prefix("FOO=1 RTK_DISABLED=1 cargo test"));
|
|
2002
|
-
assert!(has_rtk_disabled_prefix(
|
|
2003
|
-
"RTK_DISABLED=true git log --oneline"
|
|
2004
|
-
));
|
|
2005
|
-
assert!(!has_rtk_disabled_prefix("git status"));
|
|
2006
|
-
assert!(!has_rtk_disabled_prefix("rtk git status"));
|
|
2007
|
-
assert!(!has_rtk_disabled_prefix("SOME_VAR=1 git status"));
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
#[test]
|
|
2011
|
-
fn test_strip_disabled_prefix() {
|
|
2012
|
-
assert_eq!(
|
|
2013
|
-
strip_disabled_prefix("RTK_DISABLED=1 git status"),
|
|
2014
|
-
"git status"
|
|
2015
|
-
);
|
|
2016
|
-
assert_eq!(
|
|
2017
|
-
strip_disabled_prefix("FOO=1 RTK_DISABLED=1 cargo test"),
|
|
2018
|
-
"cargo test"
|
|
2019
|
-
);
|
|
2020
|
-
assert_eq!(strip_disabled_prefix("git status"), "git status");
|
|
2021
|
-
}
|
|
2022
|
-
}
|