sway_command/lib.rs
1#![warn(missing_docs)]
2//! Implements a builder for swaymsg.
3use std::vec;
4
5use commands::{CriterialessCommand, SubCommand};
6use criteria::{Criteria, CriteriaList};
7use derive_more::{AsRef, Display, From};
8
9/// Contains the types for command creation
10pub mod commands;
11/// Contains the types for criteria creation
12pub mod criteria;
13
14// TODO make AsRef a feature (maybe)
15// Without it you'd just call `.to_string()`
16// AsRef necessitates updating the `rep` string on every change... might be not
17// ideal performance wise, but maybe also doesn't matter as long as you don't
18// add criteria after the fact
19/// Create a command list able to be run via sway ipc
20#[derive(AsRef, Default)]
21pub struct CommandList {
22 // To be able to implement `AsRef<str>`
23 #[as_ref(forward)]
24 rep: String,
25 commands: Vec<Command>,
26}
27
28#[doc(hidden)]
29pub fn normalize_whitespace(value: impl AsRef<str>) -> String {
30 value
31 .as_ref()
32 .split_whitespace()
33 .collect::<Vec<_>>()
34 .join(" ")
35}
36impl CommandList {
37 /// Get the commands
38 pub fn get_commands(&self) -> &[Command] {
39 &self.commands
40 }
41 /// ```
42 /// # use sway_command::*;
43 /// # use sway_command::normalize_whitespace;
44 /// # use sway_command::commands::*;
45 /// # use sway_command::criteria::*;
46 /// let cmd = CommandList::default()
47 /// .command("workspace 5")
48 /// .command(SubCommand::Border(Border::None))
49 /// .command(
50 /// CriteriaCommand::default()
51 /// .criteria(Criteria::Floating)
52 /// .command(SubCommand::Floating(EnDisTog::Disable)),
53 /// )
54 /// .command(CriterialessCommand::Bindsym(
55 /// Default::default(),
56 /// SymKey::key("a"),
57 /// SubCommand::Exit.into(),
58 /// ));
59 /// let cmd: &str = cmd.as_ref();
60 /// assert_eq!(
61 /// normalize_whitespace(cmd),
62 /// "workspace 5;border none;[floating]floating disable;bindsym a exit"
63 /// );
64 /// ```
65 pub fn command(mut self, command: impl Into<Command>) -> Self {
66 let command = command.into();
67 if !self.commands.is_empty() {
68 self.rep.push(';');
69 }
70 self.rep.push_str(command.to_string().as_ref());
71 self.commands.push(command);
72 self
73 }
74}
75
76// TODO https://github.com/JelteF/derive_more/issues/219
77// #[derive(AsRef)]
78/// A Command that can be added to a [`CommandList`] or run directly
79#[derive(Display, From)]
80pub enum Command {
81 // #[as_ref(forward)]
82 /// A Command that contains criteria
83 #[from(types(SubCommand))]
84 Criteria(CriteriaCommand),
85 /// A Command without Criteria
86 #[from(types(CriterialessCommand))]
87 Criterialess(Box<CriterialessCommand>),
88 // #[from(types("&str"))]
89 /// Untyped Command
90 #[from(forward)]
91 Raw(String),
92}
93
94#[derive(AsRef, Display, Default, Clone)]
95#[display(fmt = "{rep}")]
96/// A command with an optional Criteria
97pub struct CriteriaCommand {
98 // To be able to implement `AsRef<str>`
99 #[as_ref(forward)]
100 rep: String,
101 criteria: Option<CriteriaList>,
102 commands: Vec<SubCommand>,
103}
104
105impl From<SubCommand> for CriteriaCommand {
106 fn from(cmd: SubCommand) -> Self {
107 Self {
108 rep: cmd.to_string(),
109 commands: vec![cmd],
110 criteria: Default::default(),
111 }
112 }
113}
114
115impl CriteriaCommand {
116 /// Get the commands in CriteriaCommand
117 pub fn get_commands(&self) -> &[SubCommand] {
118 &self.commands
119 }
120 /// At a new command
121 pub fn command(mut self, command: SubCommand) -> Self {
122 if !self.commands.is_empty() {
123 self.rep.push(',');
124 }
125 self.rep.push_str(&command.to_string());
126 self.commands.push(command);
127 self
128 }
129 /// Preformance note:
130 ///
131 /// When adding criteria after adding the first commands, the string
132 /// representation needs to be rebuild
133 pub fn criteria(mut self, criteria: Criteria) -> Self {
134 if self.commands.is_empty() && self.criteria.is_some() {
135 let Some(criterias) = &mut self.criteria else { unreachable!() };
136 criterias.criteria(criteria);
137 // TODO investigate if this could be replaced with `self.rep =
138 // criterias.to_string()`
139 assert_eq!(self.rep.pop(), Some(']'));
140 self.rep.push_str(" {criteria}]")
141 } else {
142 if let Some(criterias) = &mut self.criteria {
143 criterias.criteria(criteria);
144 self.rep = String::with_capacity(self.rep.len());
145 self.rep.push_str(criterias.as_ref());
146 } else {
147 self.criteria = Some(CriteriaList::new(criteria));
148 self.rep = self.criteria.as_ref().unwrap().to_string();
149 }
150 // TODO no need to rebuild, just copy the original string here, just need to
151 // remember where the commands start.
152 if !self.commands.is_empty() {
153 self.rep.push_str(&self.commands[0].to_string());
154 for command in &self.commands[1..] {
155 self.rep.push(',');
156 self.rep.push_str(&command.to_string());
157 }
158 }
159 }
160 self
161 }
162}