refactor(power): rename sys provider to power (D13)
Per D13 in the v2 plan: 'sys' collides mentally with 'systemd'. The
power & session provider becomes 'power' everywhere. Pre-v2 names
('sys', 'system', badge_sys) remain accepted as serde aliases so
existing user configs keep parsing.
Also fixes a pre-existing bug: filter.rs mapped :sys/:system/:power
prefixes to the type_id 'system', but the provider exposed itself as
Plugin('sys'). The two never matched, so prefix filtering on this
provider was a no-op. Now everything (filter table, ProviderType
FromStr, provider's own provider_type, config key) agrees on 'power'.
Files renamed: providers/system.rs -> providers/power.rs
Struct renamed: SystemProvider -> PowerProvider
type_id: 'sys' -> 'power'
Item IDs: 'sys:shutdown' -> 'power:shutdown' (frecency for the 7
power items resets after upgrade — acceptable for a v2 break)
Config key: providers.system -> providers.power (alias 'system', 'sys')
Theme color: colors.badge_sys -> colors.badge_power (alias 'badge_sys').
theme.rs emits both --owlry-badge-power and --owlry-badge-sys so
existing stylesheets keep rendering.
UI provider_meta: 'system' arm becomes 'power' | 'system'
ProviderType::FromStr: 'power', 'sys', 'system' all -> Plugin('power')
(and 'uuctl', 'systemd' -> Plugin('uuctl') as parallel hygiene)
Tests added (TDD):
- provider_type_from_str_maps_power_aliases
- provider_type_from_str_maps_systemd_aliases
- providers_config_accepts_power_key
- providers_config_accepts_pre_v2_system_alias
- theme_colors_accepts_pre_v2_badge_sys_alias
- all_item_ids_use_power_prefix (in power.rs)
239 tests pass (up from 234) with --features full. Task #8 complete.
This commit is contained in:
@@ -103,7 +103,8 @@ pub struct ThemeColors {
|
||||
pub badge_file: Option<String>,
|
||||
pub badge_script: Option<String>,
|
||||
pub badge_ssh: Option<String>,
|
||||
pub badge_sys: Option<String>,
|
||||
#[serde(alias = "badge_sys")]
|
||||
pub badge_power: Option<String>,
|
||||
pub badge_uuctl: Option<String>,
|
||||
pub badge_web: Option<String>,
|
||||
// Widget badge colors
|
||||
@@ -168,9 +169,10 @@ pub struct ProvidersConfig {
|
||||
/// Enable built-in unit/currency converter (> trigger)
|
||||
#[serde(default = "default_true")]
|
||||
pub converter: bool,
|
||||
/// Enable built-in system actions (shutdown, reboot, lock, etc.)
|
||||
#[serde(default = "default_true")]
|
||||
pub system: bool,
|
||||
/// Enable built-in power actions (shutdown, reboot, lock, etc.)
|
||||
/// Pre-v2 config key `system` is still accepted as an alias.
|
||||
#[serde(default = "default_true", alias = "system", alias = "sys")]
|
||||
pub power: bool,
|
||||
/// Enable systemd user units provider (alias: uuctl)
|
||||
#[serde(default = "default_true", alias = "uuctl")]
|
||||
pub systemd: bool,
|
||||
@@ -212,7 +214,7 @@ impl Default for ProvidersConfig {
|
||||
commands: true,
|
||||
calculator: true,
|
||||
converter: true,
|
||||
system: true,
|
||||
power: true,
|
||||
systemd: true,
|
||||
bookmarks: true,
|
||||
clipboard: true,
|
||||
@@ -612,3 +614,38 @@ mod tests {
|
||||
assert!(!super::command_exists("owlry_nonexistent_binary_abc123"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod v2_rename_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn providers_config_accepts_power_key() {
|
||||
let toml = r#"[providers]
|
||||
power = false
|
||||
"#;
|
||||
let cfg: Config = toml::from_str(toml).expect("must parse");
|
||||
assert!(!cfg.providers.power);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn providers_config_accepts_pre_v2_system_alias() {
|
||||
// Pre-v2 configs used `system = ...`. Serde alias keeps them working.
|
||||
let toml = r#"[providers]
|
||||
system = false
|
||||
"#;
|
||||
let cfg: Config = toml::from_str(toml).expect("must parse");
|
||||
assert!(!cfg.providers.power);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_colors_accepts_pre_v2_badge_sys_alias() {
|
||||
// Pre-v2 stylesheets named the color `badge_sys`. Serde alias keeps
|
||||
// existing user configs working.
|
||||
let toml = r##"[appearance.colors]
|
||||
badge_sys = "#ff8800"
|
||||
"##;
|
||||
let cfg: Config = toml::from_str(toml).expect("must parse");
|
||||
assert_eq!(cfg.appearance.colors.badge_power.as_deref(), Some("#ff8800"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,9 +241,9 @@ impl ProviderFilter {
|
||||
("script", "scripts"),
|
||||
("scripts", "scripts"),
|
||||
("ssh", "ssh"),
|
||||
("sys", "system"),
|
||||
("system", "system"),
|
||||
("power", "system"),
|
||||
("sys", "power"),
|
||||
("system", "power"),
|
||||
("power", "power"),
|
||||
("uuctl", "uuctl"),
|
||||
("systemd", "uuctl"),
|
||||
("web", "websearch"),
|
||||
|
||||
@@ -4,7 +4,7 @@ mod command;
|
||||
pub mod dmenu;
|
||||
pub(crate) mod calculator;
|
||||
pub(crate) mod converter;
|
||||
pub(crate) mod system;
|
||||
pub(crate) mod power;
|
||||
|
||||
// Optional feature-gated providers
|
||||
#[cfg(feature = "bookmarks")]
|
||||
@@ -137,6 +137,10 @@ impl std::str::FromStr for ProviderType {
|
||||
"app" | "apps" | "application" | "applications" => Ok(ProviderType::Application),
|
||||
"cmd" | "cmds" | "command" | "commands" => Ok(ProviderType::Command),
|
||||
"dmenu" => Ok(ProviderType::Dmenu),
|
||||
// Power provider — `sys` and `system` are pre-v2 muscle-memory aliases.
|
||||
"power" | "sys" | "system" => Ok(ProviderType::Plugin("power".into())),
|
||||
// systemd provider — type_id is `uuctl` (CLI back-compat).
|
||||
"uuctl" | "systemd" => Ok(ProviderType::Plugin("uuctl".into())),
|
||||
other => Ok(ProviderType::Plugin(other.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -268,9 +272,9 @@ impl ProviderManager {
|
||||
Box::new(CommandProvider::new()),
|
||||
];
|
||||
|
||||
if cfg_snapshot.system {
|
||||
core_providers.push(Box::new(system::SystemProvider::new()));
|
||||
info!("Registered built-in system provider");
|
||||
if cfg_snapshot.power {
|
||||
core_providers.push(Box::new(power::PowerProvider::new()));
|
||||
info!("Registered built-in power provider");
|
||||
}
|
||||
|
||||
#[cfg(feature = "bookmarks")]
|
||||
@@ -937,6 +941,41 @@ mod tests {
|
||||
assert_eq!(ProviderPosition::Widget.as_str(), "widget");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_type_from_str_maps_power_aliases() {
|
||||
// v2 renamed `sys` -> `power`. `sys` and `system` are kept as aliases
|
||||
// for muscle memory; they must all resolve to Plugin("power"), never
|
||||
// Plugin("sys") or Plugin("system").
|
||||
use std::str::FromStr;
|
||||
assert_eq!(
|
||||
ProviderType::from_str("power").unwrap(),
|
||||
ProviderType::Plugin("power".into())
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderType::from_str("sys").unwrap(),
|
||||
ProviderType::Plugin("power".into())
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderType::from_str("system").unwrap(),
|
||||
ProviderType::Plugin("power".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_type_from_str_maps_systemd_aliases() {
|
||||
// systemd provider's type_id is `uuctl` (CLI back-compat from pre-v2).
|
||||
// Both `uuctl` and `systemd` must resolve to Plugin("uuctl").
|
||||
use std::str::FromStr;
|
||||
assert_eq!(
|
||||
ProviderType::from_str("uuctl").unwrap(),
|
||||
ProviderType::Plugin("uuctl".into())
|
||||
);
|
||||
assert_eq!(
|
||||
ProviderType::from_str("systemd").unwrap(),
|
||||
ProviderType::Plugin("uuctl".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_type_from_str_accepts_plural_aliases() {
|
||||
// After the demolition, FromStr accepts both singular and plural aliases.
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
use super::{ItemSource, LaunchItem, Provider, ProviderType};
|
||||
|
||||
/// Built-in system provider. Returns a fixed set of power and session management actions.
|
||||
/// Built-in power & session provider. Returns a fixed set of shutdown / reboot /
|
||||
/// suspend / lock / logout actions.
|
||||
///
|
||||
/// This is a static provider — items are populated in `new()` and `refresh()` is a no-op.
|
||||
pub(crate) struct SystemProvider {
|
||||
/// Static provider — items are populated in `new()` and `refresh()` is a no-op.
|
||||
///
|
||||
/// type_id is `"power"`. CLI aliases `:sys` and `:system` still resolve here for
|
||||
/// muscle-memory back-compat (see [`crate::providers::ProviderType::FromStr`]
|
||||
/// and [`crate::filter::ProviderFilter::parse_query`]).
|
||||
pub(crate) struct PowerProvider {
|
||||
items: Vec<LaunchItem>,
|
||||
}
|
||||
|
||||
impl SystemProvider {
|
||||
const TYPE_ID: &str = "power";
|
||||
|
||||
impl PowerProvider {
|
||||
pub fn new() -> Self {
|
||||
let commands: &[(&str, &str, &str, &str, &str)] = &[
|
||||
(
|
||||
@@ -64,14 +71,14 @@ impl SystemProvider {
|
||||
let items = commands
|
||||
.iter()
|
||||
.map(|(action_id, name, description, icon, command)| LaunchItem {
|
||||
id: format!("sys:{}", action_id),
|
||||
id: format!("power:{}", action_id),
|
||||
name: name.to_string(),
|
||||
description: Some(description.to_string()),
|
||||
icon: Some(icon.to_string()),
|
||||
provider: ProviderType::Plugin("sys".into()),
|
||||
provider: ProviderType::Plugin(TYPE_ID.into()),
|
||||
command: command.to_string(),
|
||||
terminal: false,
|
||||
tags: vec!["system".into()],
|
||||
tags: vec!["power".into()],
|
||||
source: ItemSource::Core,
|
||||
})
|
||||
.collect();
|
||||
@@ -80,13 +87,13 @@ impl SystemProvider {
|
||||
}
|
||||
}
|
||||
|
||||
impl Provider for SystemProvider {
|
||||
impl Provider for PowerProvider {
|
||||
fn name(&self) -> &str {
|
||||
"System"
|
||||
"Power"
|
||||
}
|
||||
|
||||
fn provider_type(&self) -> ProviderType {
|
||||
ProviderType::Plugin("sys".into())
|
||||
ProviderType::Plugin(TYPE_ID.into())
|
||||
}
|
||||
|
||||
fn refresh(&mut self) {
|
||||
@@ -96,6 +103,22 @@ impl Provider for SystemProvider {
|
||||
fn items(&self) -> &[LaunchItem] {
|
||||
&self.items
|
||||
}
|
||||
|
||||
fn prefix(&self) -> Option<&str> {
|
||||
Some(":power")
|
||||
}
|
||||
|
||||
fn icon(&self) -> &str {
|
||||
"system-shutdown"
|
||||
}
|
||||
|
||||
fn tab_label(&self) -> Option<&str> {
|
||||
Some("Power")
|
||||
}
|
||||
|
||||
fn search_noun(&self) -> Option<&str> {
|
||||
Some("power actions")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -104,13 +127,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn has_seven_actions() {
|
||||
let provider = SystemProvider::new();
|
||||
let provider = PowerProvider::new();
|
||||
assert_eq!(provider.items().len(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains_expected_action_names() {
|
||||
let provider = SystemProvider::new();
|
||||
let provider = PowerProvider::new();
|
||||
let names: Vec<&str> = provider.items().iter().map(|i| i.name.as_str()).collect();
|
||||
assert!(names.contains(&"Shutdown"));
|
||||
assert!(names.contains(&"Reboot"));
|
||||
@@ -119,14 +142,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_type_is_sys_plugin() {
|
||||
let provider = SystemProvider::new();
|
||||
assert_eq!(provider.provider_type(), ProviderType::Plugin("sys".into()));
|
||||
fn provider_type_is_power_plugin() {
|
||||
let provider = PowerProvider::new();
|
||||
assert_eq!(provider.provider_type(), ProviderType::Plugin("power".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_command_is_correct() {
|
||||
let provider = SystemProvider::new();
|
||||
let provider = PowerProvider::new();
|
||||
let shutdown = provider
|
||||
.items()
|
||||
.iter()
|
||||
@@ -136,14 +159,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_items_have_system_tag() {
|
||||
let provider = SystemProvider::new();
|
||||
fn all_items_have_power_tag() {
|
||||
let provider = PowerProvider::new();
|
||||
for item in provider.items() {
|
||||
assert!(
|
||||
item.tags.contains(&"system".to_string()),
|
||||
"item '{}' is missing 'system' tag",
|
||||
item.tags.contains(&"power".to_string()),
|
||||
"item '{}' is missing 'power' tag",
|
||||
item.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_item_ids_use_power_prefix() {
|
||||
// Frecency / IPC compatibility check: every item id must start with
|
||||
// the canonical "power:" prefix after the v2 rename.
|
||||
let provider = PowerProvider::new();
|
||||
for item in provider.items() {
|
||||
assert!(
|
||||
item.id.starts_with("power:"),
|
||||
"item id '{}' should start with 'power:'",
|
||||
item.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,8 +71,10 @@ pub fn generate_variables_css(config: &AppearanceConfig) -> String {
|
||||
if let Some(ref badge_ssh) = config.colors.badge_ssh {
|
||||
css.push_str(&format!(" --owlry-badge-ssh: {};\n", badge_ssh));
|
||||
}
|
||||
if let Some(ref badge_sys) = config.colors.badge_sys {
|
||||
css.push_str(&format!(" --owlry-badge-sys: {};\n", badge_sys));
|
||||
if let Some(ref badge_power) = config.colors.badge_power {
|
||||
// Emit both for transition: pre-v2 stylesheets reference --owlry-badge-sys.
|
||||
css.push_str(&format!(" --owlry-badge-power: {};\n", badge_power));
|
||||
css.push_str(&format!(" --owlry-badge-sys: {};\n", badge_power));
|
||||
}
|
||||
if let Some(ref badge_uuctl) = config.colors.badge_uuctl {
|
||||
css.push_str(&format!(" --owlry-badge-uuctl: {};\n", badge_uuctl));
|
||||
|
||||
@@ -396,8 +396,8 @@ impl MainWindow {
|
||||
if config.converter {
|
||||
parts.push("> conv".to_string());
|
||||
}
|
||||
if config.system {
|
||||
parts.push(":sys".to_string());
|
||||
if config.power {
|
||||
parts.push(":power".to_string());
|
||||
}
|
||||
|
||||
parts.join(" ")
|
||||
@@ -595,7 +595,7 @@ impl MainWindow {
|
||||
"pomodoro" => "pomodoro",
|
||||
"scripts" => "scripts",
|
||||
"ssh" => "SSH hosts",
|
||||
"system" => "system",
|
||||
"power" | "system" => "power actions",
|
||||
"uuctl" => "uuctl units",
|
||||
"weather" => "weather",
|
||||
"websearch" => "web",
|
||||
|
||||
@@ -86,10 +86,10 @@ fn hardcoded(provider: &ProviderType) -> ProviderMeta {
|
||||
css_class: "owlry-filter-ssh".to_string(),
|
||||
search_noun: "SSH hosts".to_string(),
|
||||
},
|
||||
"system" => ProviderMeta {
|
||||
tab_label: "System".to_string(),
|
||||
css_class: "owlry-filter-sys".to_string(),
|
||||
search_noun: "system".to_string(),
|
||||
"power" | "system" => ProviderMeta {
|
||||
tab_label: "Power".to_string(),
|
||||
css_class: "owlry-filter-power".to_string(),
|
||||
search_noun: "power actions".to_string(),
|
||||
},
|
||||
"uuctl" => ProviderMeta {
|
||||
tab_label: "uuctl".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user