1pub const ALIAS_EMPTY: &str = "Alias can't be empty. Use --alias to specify one.";
4pub const ALIAS_WHITESPACE: &str =
5 "Alias can't contain whitespace. Use --alias to pick a simpler name.";
6pub const ALIAS_PATTERN_CHARS: &str =
7 "Alias can't contain pattern characters. Use --alias to pick a different name.";
8pub const HOSTNAME_WHITESPACE: &str = "Hostname can't contain whitespace.";
9pub const USER_WHITESPACE: &str = "User can't contain whitespace.";
10pub const PASSWORD_EMPTY: &str = "Password can't be empty.";
11pub const CANCELLED: &str = "Cancelled.";
12pub use super::SNIPPET_DESCRIPTION_CONTROL_CHARS as DESCRIPTION_CONTROL_CHARS;
15
16pub use super::contains_control_chars as control_chars;
17
18pub use super::welcome_aboard as welcome;
19
20pub fn gen_assets_done(count: usize, dir: &str) -> String {
24 format!("Wrote {count} SVG asset(s) to {dir}.")
25}
26
27pub fn gen_assets_hero_done(path: &str) -> String {
30 format!("Wrote animated hero SVG to {path}.")
31}
32
33pub const IMPORT_NO_FILE: &str =
36 "Provide a file or use --known-hosts. Run 'purple import --help' for details.";
37
38pub const NO_PROVIDERS: &str = "No providers configured. Run 'purple provider add' to set one up.";
41
42pub const PROVIDER_LIST: &str = "digitalocean, vultr, linode, hetzner, upcloud, proxmox, aws, \
46 scaleway, gcp, azure, tailscale, oracle, ovh, leaseweb, i3d, transip";
47
48pub fn unknown_provider(name: &str) -> String {
52 format!("Never heard of '{}'. Try: {}.", name, PROVIDER_LIST)
53}
54
55pub fn skipping_unknown_provider(name: &str) -> String {
59 format!(
60 "Skipping unknown provider '{}'. Try: {}.",
61 name, PROVIDER_LIST
62 )
63}
64
65pub fn alias_already_exists(alias: &str) -> String {
68 format!(
69 "'{}' already exists. Use --alias to pick a different name.",
70 alias
71 )
72}
73
74pub fn import_parse_failures(count: usize) -> String {
77 let s = if count == 1 { "" } else { "s" };
78 format!(
79 "! {} line{} could not be parsed (invalid format).",
80 count, s
81 )
82}
83
84pub fn import_read_errors(count: usize) -> String {
85 let s = if count == 1 { "" } else { "s" };
86 format!("! {} line{} could not be read (encoding error).", count, s)
87}
88
89pub fn no_config_for(provider: &str) -> String {
90 format!(
91 "No configuration for {}. Run 'purple provider add {}' first.",
92 provider, provider
93 )
94}
95
96pub fn saved_config(provider: &str) -> String {
97 format!("Saved {} configuration.", provider)
98}
99
100pub fn no_config_to_remove(provider: &str) -> String {
101 format!("No configuration for '{}'. Nothing to remove.", provider)
102}
103
104pub fn removed_config(provider: &str) -> String {
105 format!("Removed {} configuration.", provider)
106}
107
108pub fn removed_configs(provider: &str, count: usize) -> String {
109 format!("Removed {} {} configurations.", count, provider)
110}
111
112pub fn invalid_label_flag(reason: &str) -> String {
113 format!("Invalid --label: {}", reason)
114}
115
116pub fn add_requires_label(provider: &str) -> String {
117 format!(
118 "Provider '{}' already has labeled configs. Pass --label to add another.",
119 provider
120 )
121}
122
123pub fn add_label_collides_with_bare(provider: &str) -> String {
124 format!(
125 "Provider '{}' has a bare config. Remove it first or use the TUI add flow which prompts for labels.",
126 provider
127 )
128}
129
130pub fn no_tunnels_for(alias: &str) -> String {
133 format!("No tunnels configured for {}.", alias)
134}
135
136pub fn tunnels_for(alias: &str) -> String {
137 format!("Tunnels for {}:", alias)
138}
139
140pub const NO_TUNNELS: &str = "No tunnels configured.";
141
142pub fn starting_tunnel(alias: &str) -> String {
143 format!("Starting tunnel for {}... (Ctrl+C to stop)", alias)
144}
145
146pub fn host_not_found(alias: &str) -> String {
147 format!("No host '{}' found.", alias)
148}
149
150pub fn added_forward(forward: &str, alias: &str) -> String {
151 format!("Added {} to {}.", forward, alias)
152}
153
154pub fn forward_exists(forward: &str, alias: &str) -> String {
155 format!("Forward {} already exists on {}.", forward, alias)
156}
157
158pub fn forward_not_found(forward: &str, alias: &str) -> String {
159 format!("No matching forward {} found on {}.", forward, alias)
160}
161
162pub fn removed_forward(forward: &str, alias: &str) -> String {
163 format!("Removed {} from {}.", forward, alias)
164}
165
166pub fn no_forwards(alias: &str) -> String {
167 format!("No forwarding directives configured for '{}'.", alias)
168}
169
170pub fn save_config_failed(e: &impl std::fmt::Display) -> String {
171 format!("Failed to save config: {}", e)
172}
173
174pub fn included_host_read_only(alias: &str) -> String {
175 format!(
176 "Host '{}' is from an included file and cannot be modified.",
177 alias
178 )
179}
180
181pub fn operation_failed(e: &impl std::fmt::Display) -> String {
182 format!("Failed: {}", e)
183}
184
185pub const NO_SNIPPETS: &str = "No snippets configured. Use 'purple snippet add' to create one.";
188
189pub use super::snippet_added;
190pub use super::snippet_removed;
191pub use super::snippet_updated;
192
193pub fn snippet_not_found(name: &str) -> String {
194 format!("No snippet '{}' found.", name)
195}
196
197pub fn no_hosts_with_tag(tag: &str) -> String {
198 format!("No hosts found with tag '{}'.", tag)
199}
200
201pub const SPECIFY_TARGET: &str = "Specify a host alias, --tag or --all.";
202
203pub fn beaming_up(alias: &str) -> String {
206 format!("Beaming you up to {}...\n", alias)
207}
208
209pub fn running_snippet_on(name: &str, alias: &str) -> String {
210 format!("Running '{}' on {}...\n", name, alias)
211}
212
213pub fn host_separator(alias: &str) -> String {
214 format!("── {} ──", alias)
215}
216
217pub fn exited_with_code(code: i32) -> String {
218 format!("Exited with code {}.", code)
219}
220
221pub const DONE: &str = "Done.";
222
223pub fn done_multi(name: &str, count: usize) -> String {
224 format!("Done. Ran '{}' on {} hosts.", name, count)
225}
226
227pub const PRESS_ENTER: &str = "Press Enter to continue...";
228
229pub fn host_failed(alias: &str, e: &impl std::fmt::Display) -> String {
230 format!("[{}] Failed: {}", alias, e)
231}
232
233pub fn skipping_host(alias: &str, e: &impl std::fmt::Display) -> String {
234 format!("Skipping {}: {}", alias, e)
235}
236
237pub fn password_removed(alias: &str) -> String {
240 format!("Password removed for {}.", alias)
241}
242
243pub fn log_deleted(path: &impl std::fmt::Display) -> String {
246 format!("Log file deleted: {}", path)
247}
248
249pub fn no_log_file(path: &impl std::fmt::Display) -> String {
250 format!("No log file found at {}", path)
251}
252
253pub const BUILTIN_THEMES: &str = "Built-in themes:";
256pub const CUSTOM_THEMES: &str = "\nCustom themes:";
257
258pub fn theme_set(name: &str) -> String {
259 format!("Theme set to: {}", name)
260}
261
262pub fn syncing(name: &str, summary: &str) -> String {
265 format!("\x1b[2K\rSyncing {}... {}", name, summary)
266}
267
268pub fn syncing_start(name: &str) -> String {
273 format!("Syncing {}... ", name)
274}
275
276pub fn vault_signing_host(alias: &str) -> String {
280 format!("Signing {}... ", alias)
281}
282
283pub fn vault_sign_host_block_gone(alias: &str) -> String {
288 format!(
289 " warning: {} no longer in ssh config; CertificateFile not written (cert saved on disk)",
290 alias
291 )
292}
293
294pub const SYNC_FAILED: &str = "failed.";
298
299pub fn servers_found_with_failures(count: usize, failures: usize, total: usize) -> String {
300 format!(
301 "{} servers found ({} of {} failed to fetch).",
302 count, failures, total
303 )
304}
305
306pub fn servers_found(count: usize) -> String {
307 format!("{} servers found.", count)
308}
309
310pub fn sync_result(prefix: &str, added: usize, updated: usize, unchanged: usize) -> String {
311 format!(
312 "{}Added {}, updated {}, unchanged {}.",
313 prefix, added, updated, unchanged
314 )
315}
316
317pub fn sync_removed(count: usize) -> String {
318 format!(" Removed {}.", count)
319}
320
321pub fn sync_stale(count: usize) -> String {
322 format!(" Marked {} stale.", count)
323}
324
325pub fn sync_skip_remove(display_name: &str) -> String {
326 format!(
327 "! {}: skipping --remove due to partial failures.",
328 display_name
329 )
330}
331
332pub fn sync_error(display_name: &str, e: &impl std::fmt::Display) -> String {
333 format!("! {}: {}", display_name, e)
334}
335
336pub const SYNC_SKIP_WRITE: &str =
337 "! Skipping config write due to sync failures. Fix the errors and re-run.";
338
339pub const PROXMOX_URL_REQUIRED: &str =
342 "Proxmox requires --url (e.g. --url https://pve.example.com:8006).";
343pub const AWS_REGIONS_REQUIRED: &str =
344 "AWS requires --regions (e.g. --regions us-east-1,eu-west-1).";
345pub const AZURE_REGIONS_REQUIRED: &str =
346 "Azure requires --regions with one or more subscription IDs.";
347pub const GCP_PROJECT_REQUIRED: &str = "GCP requires --project (e.g. --project my-gcp-project-id).";
348pub use super::ALIAS_PREFIX_INVALID;
349
350pub const WARN_URL_NOT_USED: &str =
351 "Warning: --url is only used by the Proxmox provider. Ignoring.";
352pub const WARN_PROFILE_NOT_USED: &str =
353 "Warning: --profile is only used by the AWS provider. Ignoring.";
354pub const WARN_PROJECT_NOT_USED: &str =
355 "Warning: --project is only used by the GCP provider. Ignoring.";
356pub const WARN_COMPARTMENT_NOT_USED: &str =
357 "Warning: --compartment is only used by the Oracle provider. Ignoring.";
358pub const WARN_NO_VERIFY_TLS_NOT_USED: &str =
359 "Warning: --no-verify-tls is only used by the Proxmox provider. Ignoring.";
360pub const WARN_VERIFY_TLS_NOT_USED: &str =
361 "Warning: --verify-tls is only used by the Proxmox provider. Ignoring.";
362pub const WARN_REGIONS_NOT_USED: &str = "Warning: --regions is only used by the AWS, Scaleway, GCP, Azure and Oracle providers. \
363 Ignoring.";
364
365pub const SYNC_RESULT_PREFIX_LIVE: &str = " ";
370pub const SYNC_RESULT_PREFIX_DRY_RUN: &str = " Would have: ";
371
372pub const PROVIDER_URL_REQUIRES_HTTPS: &str =
377 "URL must start with https://. For self-signed certificates use --no-verify-tls.";
378
379pub use super::PROVIDER_TOKEN_REQUIRED_GCP;
383pub use super::PROVIDER_TOKEN_REQUIRED_ORACLE;
384pub use super::azure_subscription_id_invalid;
385pub use super::provider_token_required;
386
387pub const SCALEWAY_REGIONS_REQUIRED: &str = "Scaleway requires --regions with one or more zones \
391 (e.g. --regions fr-par-1,nl-ams-1).";
392
393pub const ORACLE_COMPARTMENT_REQUIRED: &str =
394 "Oracle requires --compartment (e.g. --compartment ocid1.compartment.oc1..aaa...).";
395
396pub fn vault_no_role(alias: &str) -> String {
399 format!(
400 "No Vault SSH role configured for '{}'. Set it in the host form \
401 (Vault SSH Role field) or in the provider config (vault_role).",
402 alias
403 )
404}
405
406pub fn vault_cert_signed(path: &impl std::fmt::Display) -> String {
407 format!("Certificate signed: {}", path)
408}
409
410pub fn vault_sign_failed(e: &impl std::fmt::Display) -> String {
411 format!("failed: {}", e)
412}
413
414pub fn vault_config_update_warning(e: &impl std::fmt::Display) -> String {
415 format!("Warning: Failed to update SSH config: {}", e)
416}
417
418pub const NO_HOSTS: &str = "No hosts configured. Run 'purple' to add some!";
421
422pub const NO_TOKEN: &str =
425 "No token provided. Use --token, --token-stdin, or set PURPLE_TOKEN env var.";
426
427pub mod whats_new {
430 pub const HEADER: &str = "purple release notes";
431}