#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PreferenceValue {
Bool(bool),
Int(i32),
String(String),
}
impl PreferenceValue {
#[must_use]
pub fn to_js_string(&self) -> String {
match self {
Self::Bool(b) => b.to_string(),
Self::Int(i) => i.to_string(),
Self::String(s) => format!("\"{}\"", escape_js_string(s)),
}
}
}
impl From<bool> for PreferenceValue {
#[inline]
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<i32> for PreferenceValue {
#[inline]
fn from(value: i32) -> Self {
Self::Int(value)
}
}
impl From<String> for PreferenceValue {
#[inline]
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for PreferenceValue {
#[inline]
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
#[derive(Debug, Clone)]
pub struct FirefoxPreference {
pub key: String,
pub value: PreferenceValue,
pub comment: Option<String>,
}
impl FirefoxPreference {
#[must_use]
pub fn new(key: impl Into<String>, value: impl Into<PreferenceValue>) -> Self {
Self {
key: key.into(),
value: value.into(),
comment: None,
}
}
#[must_use]
pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
self.comment = Some(comment.into());
self
}
#[must_use]
pub fn to_user_pref_line(&self) -> String {
let mut output = String::new();
if let Some(comment) = &self.comment {
output.push_str("// ");
output.push_str(comment);
output.push('\n');
}
output.push_str(&format!(
"user_pref(\"{}\", {});",
self.key,
self.value.to_js_string()
));
output
}
}
fn escape_js_string(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
#[cfg(test)]
mod tests {
use super::{FirefoxPreference, PreferenceValue, escape_js_string};
#[test]
fn test_bool_to_js_string() {
assert_eq!(PreferenceValue::Bool(true).to_js_string(), "true");
assert_eq!(PreferenceValue::Bool(false).to_js_string(), "false");
}
#[test]
fn test_int_to_js_string() {
assert_eq!(PreferenceValue::Int(42).to_js_string(), "42");
assert_eq!(PreferenceValue::Int(-10).to_js_string(), "-10");
assert_eq!(PreferenceValue::Int(0).to_js_string(), "0");
}
#[test]
fn test_string_to_js_string() {
assert_eq!(
PreferenceValue::String("test".to_string()).to_js_string(),
"\"test\""
);
assert_eq!(
PreferenceValue::String(String::new()).to_js_string(),
"\"\""
);
}
#[test]
fn test_from_bool() {
let val: PreferenceValue = true.into();
assert_eq!(val, PreferenceValue::Bool(true));
}
#[test]
fn test_from_i32() {
let val: PreferenceValue = 42.into();
assert_eq!(val, PreferenceValue::Int(42));
}
#[test]
fn test_from_string() {
let val: PreferenceValue = String::from("test").into();
assert_eq!(val, PreferenceValue::String("test".to_string()));
}
#[test]
fn test_from_str() {
let val: PreferenceValue = "test".into();
assert_eq!(val, PreferenceValue::String("test".to_string()));
}
#[test]
fn test_escape_backslash() {
assert_eq!(escape_js_string("path\\to\\file"), "path\\\\to\\\\file");
}
#[test]
fn test_escape_quotes() {
assert_eq!(escape_js_string("with\"quotes"), "with\\\"quotes");
}
#[test]
fn test_escape_newline() {
assert_eq!(escape_js_string("line1\nline2"), "line1\\nline2");
}
#[test]
fn test_escape_carriage_return() {
assert_eq!(escape_js_string("line1\rline2"), "line1\\rline2");
}
#[test]
fn test_escape_tab() {
assert_eq!(escape_js_string("col1\tcol2"), "col1\\tcol2");
}
#[test]
fn test_escape_combined() {
assert_eq!(
escape_js_string("path\\to\n\"file\""),
"path\\\\to\\n\\\"file\\\""
);
}
#[test]
fn test_new_preference() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::Bool(true));
assert_eq!(pref.key, "test.pref");
assert_eq!(pref.value, PreferenceValue::Bool(true));
assert!(pref.comment.is_none());
}
#[test]
fn test_with_comment() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::Int(42))
.with_comment("Test comment");
assert_eq!(pref.comment, Some("Test comment".to_string()));
}
#[test]
fn test_to_user_pref_line_bool() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::Bool(true));
assert_eq!(pref.to_user_pref_line(), "user_pref(\"test.pref\", true);");
}
#[test]
fn test_to_user_pref_line_int() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::Int(42));
assert_eq!(pref.to_user_pref_line(), "user_pref(\"test.pref\", 42);");
}
#[test]
fn test_to_user_pref_line_string() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::String("value".into()));
assert_eq!(
pref.to_user_pref_line(),
"user_pref(\"test.pref\", \"value\");"
);
}
#[test]
fn test_to_user_pref_line_with_comment() {
let pref = FirefoxPreference::new("test.pref", PreferenceValue::Int(42))
.with_comment("Test preference");
let line = pref.to_user_pref_line();
assert!(line.starts_with("// Test preference\n"));
assert!(line.ends_with("user_pref(\"test.pref\", 42);"));
}
#[test]
fn test_new_with_into_value() {
let pref = FirefoxPreference::new("test.bool", false);
assert_eq!(pref.value, PreferenceValue::Bool(false));
let pref = FirefoxPreference::new("test.int", 123);
assert_eq!(pref.value, PreferenceValue::Int(123));
}
#[test]
fn test_preference_clone() {
let pref = FirefoxPreference::new("test.pref", true).with_comment("comment");
let cloned = pref.clone();
assert_eq!(pref.key, cloned.key);
assert_eq!(pref.value, cloned.value);
assert_eq!(pref.comment, cloned.comment);
}
}