#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::format;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use alloc::str::FromStr;
#[cfg(feature = "std")]
use std::str::FromStr;
#[derive(Debug, PartialEq)]
pub enum BLSValue {
Value(String),
ValueWithComment(String, String),
}
#[derive(Debug, PartialEq)]
pub enum ValueSetPolicy {
ReplaceAll,
Append,
Prepend,
InsertAt(usize),
}
#[derive(Debug, PartialEq)]
pub enum BLSKey {
Title,
Version,
MachineId,
SortKey,
Linux,
Efi,
Initrd,
Options,
Devicetree,
DevicetreeOverlay,
Architecture,
GrubHotkey,
GrubUsers,
GrubClass,
GrubArg,
}
impl FromStr for BLSKey {
type Err = String;
fn from_str(key: &str) -> Result<Self, Self::Err> {
match key {
"linux" => Ok(BLSKey::Linux),
"title" => Ok(BLSKey::Title),
"version" => Ok(BLSKey::Version),
"machine_id" | "machine-id" => Ok(BLSKey::MachineId),
"sort_key" | "sort-key" => Ok(BLSKey::SortKey),
"efi" => Ok(BLSKey::Efi),
"initrd" => Ok(BLSKey::Initrd),
"options" => Ok(BLSKey::Options),
"devicetree" => Ok(BLSKey::Devicetree),
"devicetree_overlay" | "devicetree-overlay" => Ok(BLSKey::DevicetreeOverlay),
"architecture" => Ok(BLSKey::Architecture),
"grub_hotkey" => Ok(BLSKey::GrubHotkey),
"grub_users" => Ok(BLSKey::GrubUsers),
"grub_class" => Ok(BLSKey::GrubClass),
"grub_arg" => Ok(BLSKey::GrubArg),
_ => Err(format!("Invalid key {}", key)),
}
}
}
#[derive(Debug)]
pub struct BLSEntry {
pub title: Option<BLSValue>,
pub version: Option<BLSValue>,
pub machine_id: Option<BLSValue>,
pub sort_key: Option<BLSValue>,
pub linux: BLSValue,
pub efi: Option<BLSValue>,
pub initrd: Vec<BLSValue>,
pub options: Vec<BLSValue>,
pub devicetree: Option<BLSValue>,
pub devicetree_overlay: Option<BLSValue>,
pub architecture: Option<BLSValue>,
pub grub_hotkey: Option<BLSValue>,
pub grub_users: Option<BLSValue>,
pub grub_class: Vec<BLSValue>,
pub grub_arg: Option<BLSValue>,
pub comments: Vec<String>,
}
impl BLSEntry {
pub fn new() -> BLSEntry {
BLSEntry {
title: None,
version: None,
machine_id: None,
sort_key: None,
linux: BLSValue::Value(String::new()),
efi: None,
initrd: Vec::new(),
options: Vec::new(),
devicetree: None,
devicetree_overlay: None,
architecture: None,
grub_hotkey: None,
grub_users: None,
grub_class: Vec::new(),
grub_arg: None,
comments: Vec::new(),
}
}
}
impl Default for BLSEntry {
fn default() -> Self {
Self::new()
}
}
impl BLSEntry {
pub fn parse(buffer: &str) -> Result<BLSEntry, String> {
let mut entry = BLSEntry::new();
let mut has_linux = false;
let mut has_efi = false;
for line in buffer.lines() {
let mut comment = None;
let line = if line.contains("#") {
let split: Vec<_> = line.splitn(2, "#").collect();
comment = Some(String::from(split[1]));
split[0]
} else {
line
};
if line.trim().contains(" ") {
let key_value: Vec<&str> = line.trim().splitn(2, " ").collect();
let key = BLSKey::from_str(key_value[0])?;
if key == BLSKey::Linux {
has_linux = true;
} else if key == BLSKey::Efi {
has_efi = true;
}
entry.set(
key,
String::from(key_value[1]),
comment,
ValueSetPolicy::Append,
);
} else if let Some(comment) = comment {
entry.comments.push(comment);
}
}
if has_linux || has_efi {
Ok(entry)
} else {
Err(String::from("No 'linux' or 'efi' command found."))
}
}
pub fn render(&self) -> String {
let mut content = String::new();
fn render_value(content: &mut String, key: &str, value: &BLSValue) {
content.push_str(key);
content.push(' ');
match value {
BLSValue::Value(value) => content.push_str(value),
BLSValue::ValueWithComment(value, comment) => {
content.push_str(value);
content.push_str(" #");
content.push_str(comment);
}
}
content.push('\n');
}
fn render_single_value(content: &mut String, key: &str, value: &Option<BLSValue>) {
if let Some(value) = value {
render_value(content, key, value)
}
}
fn render_multiple_values(content: &mut String, key: &str, values: &Vec<BLSValue>) {
for val in values {
render_value(content, key, val)
}
}
for comment in &self.comments {
content.push('#');
content.push_str(comment);
content.push('\n');
}
render_value(&mut content, "linux", &self.linux);
render_single_value(&mut content, "title", &self.title);
render_single_value(&mut content, "version", &self.version);
render_single_value(&mut content, "machine-id", &self.machine_id);
render_single_value(&mut content, "sort-key", &self.sort_key);
render_single_value(&mut content, "efi", &self.efi);
render_single_value(&mut content, "devicetree", &self.devicetree);
render_single_value(&mut content, "devicetree-overlay", &self.devicetree_overlay);
render_single_value(&mut content, "architecture", &self.architecture);
render_single_value(&mut content, "grub_hotkey", &self.grub_hotkey);
render_single_value(&mut content, "grub_users", &self.grub_users);
render_single_value(&mut content, "grub_arg", &self.grub_arg);
render_multiple_values(&mut content, "initrd", &self.initrd);
render_multiple_values(&mut content, "options", &self.options);
render_multiple_values(&mut content, "grub_class", &self.grub_class);
content
}
pub fn set(
&mut self,
key: BLSKey,
value: String,
comment: Option<String>,
set_policy: ValueSetPolicy,
) {
fn value_generator(value: String, comment: Option<String>) -> BLSValue {
match comment {
Some(comment) => BLSValue::ValueWithComment(value, comment),
None => BLSValue::Value(value),
}
}
fn push_value(values: &mut Vec<BLSValue>, val: BLSValue, policy: ValueSetPolicy) {
match policy {
ValueSetPolicy::Append => values.push(val),
ValueSetPolicy::InsertAt(i) => values.insert(i, val),
ValueSetPolicy::Prepend => values.insert(0, val),
ValueSetPolicy::ReplaceAll => {
values.clear();
values.push(val);
}
}
}
match key {
BLSKey::Title => self.title = Some(value_generator(value, comment)),
BLSKey::Version => self.version = Some(value_generator(value, comment)),
BLSKey::MachineId => self.machine_id = Some(value_generator(value, comment)),
BLSKey::SortKey => self.sort_key = Some(value_generator(value, comment)),
BLSKey::Linux => self.linux = value_generator(value, comment),
BLSKey::Efi => self.efi = Some(value_generator(value, comment)),
BLSKey::Devicetree => self.devicetree = Some(value_generator(value, comment)),
BLSKey::DevicetreeOverlay => {
self.devicetree_overlay = Some(value_generator(value, comment))
}
BLSKey::Architecture => self.architecture = Some(value_generator(value, comment)),
BLSKey::GrubHotkey => self.grub_hotkey = Some(value_generator(value, comment)),
BLSKey::GrubUsers => self.grub_users = Some(value_generator(value, comment)),
BLSKey::GrubArg => self.grub_arg = Some(value_generator(value, comment)),
BLSKey::Initrd => push_value(
&mut self.initrd,
value_generator(value, comment),
set_policy,
),
BLSKey::Options => push_value(
&mut self.options,
value_generator(value, comment),
set_policy,
),
BLSKey::GrubClass => push_value(
&mut self.grub_class,
value_generator(value, comment),
set_policy,
),
}
}
pub fn clear(&mut self, key: BLSKey) {
match key {
BLSKey::Linux => self.linux = BLSValue::Value(String::from("")),
BLSKey::Title => self.title = None,
BLSKey::Version => self.version = None,
BLSKey::MachineId => self.machine_id = None,
BLSKey::SortKey => self.sort_key = None,
BLSKey::Efi => self.efi = None,
BLSKey::Devicetree => self.devicetree = None,
BLSKey::DevicetreeOverlay => self.devicetree_overlay = None,
BLSKey::Architecture => self.architecture = None,
BLSKey::GrubHotkey => self.grub_hotkey = None,
BLSKey::GrubUsers => self.grub_users = None,
BLSKey::GrubArg => self.grub_arg = None,
BLSKey::Initrd => self.initrd.clear(),
BLSKey::Options => self.options.clear(),
BLSKey::GrubClass => self.grub_class.clear(),
}
}
}
#[cfg(test)]
mod bls_tests {
use core::str::FromStr;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec;
use super::BLSEntry;
use super::BLSKey;
use super::BLSValue;
use super::ValueSetPolicy;
#[test]
fn bls_key_from_str() {
assert!(BLSKey::from_str("linux").is_ok());
assert!(BLSKey::from_str("title").is_ok());
assert!(BLSKey::from_str("version").is_ok());
assert!(BLSKey::from_str("machine_id").is_ok());
assert!(BLSKey::from_str("machine-id").is_ok());
assert!(BLSKey::from_str("sort_key").is_ok());
assert!(BLSKey::from_str("sort-key").is_ok());
assert!(BLSKey::from_str("efi").is_ok());
assert!(BLSKey::from_str("initrd").is_ok());
assert!(BLSKey::from_str("options").is_ok());
assert!(BLSKey::from_str("devicetree").is_ok());
assert!(BLSKey::from_str("devicetree_overlay").is_ok());
assert!(BLSKey::from_str("devicetree-overlay").is_ok());
assert!(BLSKey::from_str("architecture").is_ok());
assert!(BLSKey::from_str("grub_hotkey").is_ok());
assert!(BLSKey::from_str("grub_users").is_ok());
assert!(BLSKey::from_str("grub_class").is_ok());
assert!(BLSKey::from_str("grub_arg").is_ok());
assert!(BLSKey::from_str("invalid_key").is_err());
}
#[test]
fn new_entry() {
let entry = BLSEntry::new();
match &entry.linux {
BLSValue::Value(linux) => assert_eq!(linux, ""),
_ => panic!("Invalid 'linux' value {:?}", entry.linux),
}
assert!(entry.title.is_none());
assert!(entry.version.is_none());
assert!(entry.machine_id.is_none());
assert!(entry.sort_key.is_none());
assert!(entry.efi.is_none());
assert_eq!(entry.initrd.len(), 0);
assert_eq!(entry.options.len(), 0);
assert!(entry.devicetree.is_none());
assert!(entry.devicetree_overlay.is_none());
assert!(entry.architecture.is_none());
assert!(entry.grub_hotkey.is_none());
assert!(entry.grub_users.is_none());
assert_eq!(entry.grub_class.len(), 0);
assert!(entry.grub_arg.is_none());
assert!(entry.comments.is_empty());
}
#[test]
fn parse_entry() {
let entry_txt = "#Comment\n\
linux foobar-2.4\n\
options foo=bar #Another Comment";
let entry = BLSEntry::parse(entry_txt);
assert!(entry.is_ok());
let entry = entry.unwrap();
assert_eq!(entry.comments.len(), 1);
assert_eq!(entry.comments[0], "Comment");
if let BLSValue::Value(linux) = entry.linux {
assert_eq!(linux, "foobar-2.4");
}
assert_eq!(entry.options.len(), 1);
match &entry.options[0] {
BLSValue::ValueWithComment(option, comment) => {
assert_eq!(option, "foo=bar");
assert_eq!(comment, "Another Comment");
}
_ => {
panic!("Invalid 'options' value {:?}", entry.options[0])
}
}
}
#[test]
fn parse_errors() {
let entry_txt = "options foo=bar";
let entry = BLSEntry::parse(entry_txt);
assert!(entry.is_err());
let entry_txt = "linux asdasdasdas\n\
invalid_command foo=bar";
let entry = BLSEntry::parse(entry_txt);
assert!(entry.is_err());
}
#[test]
fn parse_efi_only() {
let entry_txt = "title EFI App\nefi /EFI/app.efi";
let entry = BLSEntry::parse(entry_txt).expect("efi-only entry should parse");
assert!(entry.efi.is_some());
if let Some(BLSValue::Value(ref path)) = entry.efi {
assert_eq!(path, "/EFI/app.efi");
}
}
#[test]
fn parse_hyphenated_keys() {
let entry_txt = "title Fedora\nmachine-id 6a9857a393724b7a981ebb5b8495b9ea\nsort-key fedora\ndevicetree-overlay /overlay.dtbo\nlinux /vmlinuz";
let entry = BLSEntry::parse(entry_txt).expect("hyphenated keys should parse");
assert_eq!(
entry.title.as_ref().map(|v| match v {
BLSValue::Value(s) => s.as_str(),
_ => "",
}),
Some("Fedora")
);
assert!(entry.machine_id.is_some());
assert!(entry.sort_key.is_some());
assert!(entry.devicetree_overlay.is_some());
}
#[test]
fn parse_multiple_initrd_options() {
let entry_txt =
"linux /vmlinuz\ninitrd /initrd1\ninitrd /initrd2\noptions a=1\noptions b=2";
let entry = BLSEntry::parse(entry_txt).unwrap();
assert_eq!(entry.initrd.len(), 2);
assert_eq!(entry.options.len(), 2);
}
#[test]
fn parse_round_trip() {
let entry_txt = "title Fedora 19\nsort-key fedora\nmachine-id 6a9857a393724b7a981ebb5b8495b9ea\nversion 3.8.0-2.fc19.x86_64\noptions root=UUID=abc quiet\narchitecture x64\nlinux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux\ninitrd /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/initrd";
let entry = BLSEntry::parse(entry_txt).unwrap();
let rendered = entry.render();
let entry2 = BLSEntry::parse(&rendered).unwrap();
assert_eq!(entry.title, entry2.title);
assert_eq!(entry.version, entry2.version);
assert_eq!(entry.machine_id, entry2.machine_id);
assert_eq!(entry.sort_key, entry2.sort_key);
assert_eq!(entry.linux, entry2.linux);
assert_eq!(entry.initrd, entry2.initrd);
assert_eq!(entry.options, entry2.options);
assert_eq!(entry.architecture, entry2.architecture);
}
#[test]
fn render_all_keys_including_grub() {
let mut entry = BLSEntry::new();
entry.set(
BLSKey::Linux,
String::from("/vmlinuz"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Title,
String::from("Test"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubHotkey,
String::from("t"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubUsers,
String::from("root"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubArg,
String::from("--debug"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubClass,
String::from("recovery"),
None,
ValueSetPolicy::Append,
);
let out = entry.render();
assert!(out.contains("grub_hotkey t"));
assert!(out.contains("grub_users root"));
assert!(out.contains("grub_arg --debug"));
assert!(out.contains("grub_class recovery"));
}
#[test]
fn set_every_key() {
let mut entry = BLSEntry::new();
entry.set(
BLSKey::Linux,
String::from("/vmlinuz"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Title,
String::from("T"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Version,
String::from("1.0"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::MachineId,
String::from("abc"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::SortKey,
String::from("x"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Efi,
String::from("/efi.efi"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Initrd,
String::from("/i1"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Options,
String::from("opt"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Devicetree,
String::from("/dtb"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::DevicetreeOverlay,
String::from("/overlay"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Architecture,
String::from("x64"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubHotkey,
String::from("h"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubUsers,
String::from("u"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubClass,
String::from("c"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::GrubArg,
String::from("a"),
None,
ValueSetPolicy::ReplaceAll,
);
assert!(entry.title.is_some());
assert!(entry.version.is_some());
assert!(entry.machine_id.is_some());
assert!(entry.sort_key.is_some());
assert!(entry.efi.is_some());
assert_eq!(entry.initrd.len(), 1);
assert_eq!(entry.options.len(), 1);
assert!(entry.devicetree.is_some());
assert!(entry.devicetree_overlay.is_some());
assert!(entry.architecture.is_some());
assert!(entry.grub_hotkey.is_some());
assert!(entry.grub_users.is_some());
assert_eq!(entry.grub_class.len(), 1);
assert!(entry.grub_arg.is_some());
}
#[test]
fn set_value_policies_initrd_grub_class() {
let mut entry = BLSEntry::new();
entry.set(
BLSKey::Linux,
String::from("/vmlinuz"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Initrd,
String::from("a"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Initrd,
String::from("b"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Initrd,
String::from("mid"),
None,
ValueSetPolicy::InsertAt(1),
);
assert_eq!(entry.initrd.len(), 3);
entry.set(
BLSKey::Initrd,
String::from("first"),
None,
ValueSetPolicy::Prepend,
);
assert!(matches!(entry.initrd.first(), Some(BLSValue::Value(s)) if s == "first"));
entry.set(
BLSKey::Initrd,
String::from("only"),
None,
ValueSetPolicy::ReplaceAll,
);
assert_eq!(entry.initrd.len(), 1);
entry.set(
BLSKey::GrubClass,
String::from("class1"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::GrubClass,
String::from("class2"),
None,
ValueSetPolicy::Append,
);
assert_eq!(entry.grub_class.len(), 2);
}
#[test]
fn clear_every_key() {
let mut entry = BLSEntry::new();
entry.set(
BLSKey::Linux,
String::from("/vmlinuz"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Title,
String::from("T"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Version,
String::from("1"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::MachineId,
String::from("m"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::SortKey,
String::from("s"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Efi,
String::from("e"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Initrd,
String::from("i"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Options,
String::from("o"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::Devicetree,
String::from("d"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::DevicetreeOverlay,
String::from("do"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::Architecture,
String::from("a"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubHotkey,
String::from("g"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubUsers,
String::from("u"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.set(
BLSKey::GrubClass,
String::from("c"),
None,
ValueSetPolicy::Append,
);
entry.set(
BLSKey::GrubArg,
String::from("ga"),
None,
ValueSetPolicy::ReplaceAll,
);
entry.clear(BLSKey::Title);
entry.clear(BLSKey::Version);
entry.clear(BLSKey::MachineId);
entry.clear(BLSKey::SortKey);
entry.clear(BLSKey::Efi);
entry.clear(BLSKey::Initrd);
entry.clear(BLSKey::Options);
entry.clear(BLSKey::Devicetree);
entry.clear(BLSKey::DevicetreeOverlay);
entry.clear(BLSKey::Architecture);
entry.clear(BLSKey::GrubHotkey);
entry.clear(BLSKey::GrubUsers);
entry.clear(BLSKey::GrubClass);
entry.clear(BLSKey::GrubArg);
entry.clear(BLSKey::Linux);
assert!(entry.title.is_none());
assert!(entry.version.is_none());
assert!(entry.machine_id.is_none());
assert!(entry.sort_key.is_none());
assert!(entry.efi.is_none());
assert!(entry.initrd.is_empty());
assert!(entry.options.is_empty());
assert!(entry.devicetree.is_none());
assert!(entry.devicetree_overlay.is_none());
assert!(entry.architecture.is_none());
assert!(entry.grub_hotkey.is_none());
assert!(entry.grub_users.is_none());
assert!(entry.grub_class.is_empty());
assert!(entry.grub_arg.is_none());
assert!(matches!(&entry.linux, BLSValue::Value(s) if s.is_empty()));
}
#[test]
fn bls_value_value_with_comment() {
let v = BLSValue::Value(String::from("arg"));
let vc = BLSValue::ValueWithComment(String::from("arg"), String::from("comment"));
assert!(matches!(&v, BLSValue::Value(s) if s == "arg"));
assert!(matches!(&vc, BLSValue::ValueWithComment(a, c) if a == "arg" && c == "comment"));
}
#[test]
fn set_value_policies() {
let mut entry = BLSEntry::new();
let _ = entry.set(
BLSKey::Options,
String::from("foo"),
None,
ValueSetPolicy::Append,
);
let _ = entry.set(
BLSKey::Options,
String::from("bar"),
None,
ValueSetPolicy::Append,
);
let _ = entry.set(
BLSKey::Options,
String::from("baz"),
None,
ValueSetPolicy::Append,
);
assert_eq!(
entry.options,
vec![
BLSValue::Value(String::from("foo")),
BLSValue::Value(String::from("bar")),
BLSValue::Value(String::from("baz"))
]
);
let _ = entry.set(
BLSKey::Options,
String::from("lol"),
None,
ValueSetPolicy::InsertAt(1),
);
assert_eq!(
entry.options,
vec![
BLSValue::Value(String::from("foo")),
BLSValue::Value(String::from("lol")),
BLSValue::Value(String::from("bar")),
BLSValue::Value(String::from("baz"))
]
);
let _ = entry.set(
BLSKey::Options,
String::from("wtf"),
None,
ValueSetPolicy::ReplaceAll,
);
assert_eq!(entry.options, vec![BLSValue::Value(String::from("wtf"))]);
let _ = entry.set(
BLSKey::Options,
String::from("uwu"),
None,
ValueSetPolicy::Prepend,
);
assert_eq!(
entry.options,
vec![
BLSValue::Value(String::from("uwu")),
BLSValue::Value(String::from("wtf"))
]
);
entry.clear(BLSKey::Options);
assert_eq!(entry.options, vec![]);
entry.set(
BLSKey::Title,
String::from("foobar"),
None,
ValueSetPolicy::Append,
);
assert_eq!(entry.title, Some(BLSValue::Value(String::from("foobar"))));
entry.clear(BLSKey::Title);
assert_eq!(entry.title, None);
}
}