* [pbs-devel] [PATCH proxmox] close #4263: section-config: add comment support in section-config files
@ 2023-08-25 10:55 Gabriel Goller
0 siblings, 0 replies; only message in thread
From: Gabriel Goller @ 2023-08-25 10:55 UTC (permalink / raw)
To: pbs-devel
Added support for comments in section-config files. Comments start with
'#' and need to be over the whole line (f.e. no `attribute value # comment`).
We often parse, edit then rewrite the config to the file. In that case we
save the comments in a `HashMap` and associate them either to a section
or an attribute. When deleting sections, we also delete all the containing
comments, when deleting attributes, we don't delete any comments.
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
proxmox-section-config/src/lib.rs | 259 +++++++++++++++++++++++++++++-
1 file changed, 256 insertions(+), 3 deletions(-)
diff --git a/proxmox-section-config/src/lib.rs b/proxmox-section-config/src/lib.rs
index 4441df1..1e2378a 100644
--- a/proxmox-section-config/src/lib.rs
+++ b/proxmox-section-config/src/lib.rs
@@ -105,6 +105,7 @@ enum ParseState<'a> {
pub struct SectionConfigData {
pub sections: HashMap<String, (String, Value)>,
pub order: Vec<String>,
+ pub comments: HashMap<String, Vec<String>>,
}
impl Default for SectionConfigData {
@@ -119,6 +120,7 @@ impl SectionConfigData {
Self {
sections: HashMap::new(),
order: Vec::new(),
+ comments: HashMap::new(),
}
}
@@ -353,6 +355,11 @@ impl SectionConfig {
raw += "\n"
}
+ if let Some(comments) = config.comments.get(section_id) {
+ for c in comments {
+ raw = format!("{}{}\n", raw, c);
+ }
+ }
raw += &(self.format_section_header)(type_name, section_id, section_config)?;
for (key, value) in section_config.as_object().unwrap() {
@@ -361,6 +368,13 @@ impl SectionConfig {
continue; // skip writing out id properties, they are in the section header
}
}
+ if let Some(comments) =
+ config.comments.get(&format!("{}:{}", section_id, key))
+ {
+ for c in comments {
+ raw = format!("{}{}\n", raw, c);
+ }
+ }
raw += &(self.format_section_content)(type_name, section_id, key, value)?;
}
}
@@ -373,9 +387,21 @@ impl SectionConfig {
raw += "\n"
}
+ if let Some(comments) = config.comments.get(section_id) {
+ for c in comments {
+ raw = format!("{}{}\n", raw, c);
+ }
+ }
raw += &(self.format_section_header)(type_name, section_id, section_config)?;
for (key, value) in section_config.as_object().unwrap() {
+ if let Some(comments) =
+ config.comments.get(&format!("{}:{}", section_id, key))
+ {
+ for c in comments {
+ raw = format!("{}{}\n", raw, c);
+ }
+ }
raw += &(self.format_section_content)(type_name, section_id, key, value)?;
}
}
@@ -383,6 +409,25 @@ impl SectionConfig {
bail!("unknown section type '{type_name}'");
}
}
+ // Insert the comments with property not found
+ let keys: Vec<&String> = section_config
+ .as_object()
+ .unwrap()
+ .iter()
+ .map(|c| c.0)
+ .collect();
+
+ for c in &config.comments {
+ let split: Vec<&str> = c.0.split(':').collect();
+ if split[0] == section_id
+ && split.len() > 1
+ && !keys.contains(&&split[1].to_string())
+ {
+ for comment in c.1 {
+ raw = format!("{}{}\n", raw, comment);
+ }
+ }
+ }
}
Ok(raw)
@@ -421,6 +466,7 @@ impl SectionConfig {
try_block!({
let mut result = SectionConfigData::new();
+ let mut dangling_comments: Vec<String> = Vec::new();
try_block!({
for line in raw.lines() {
@@ -428,6 +474,10 @@ impl SectionConfig {
match state {
ParseState::BeforeHeader => {
+ if line.trim().starts_with('#') {
+ dangling_comments.push(line.to_string());
+ continue;
+ }
if line.trim().is_empty() {
continue;
}
@@ -436,6 +486,13 @@ impl SectionConfig {
(self.parse_section_header)(line)
{
//println!("OKLINE: type: {} ID: {}", section_type, section_id);
+ if !dangling_comments.is_empty() {
+ result.comments.insert(
+ section_id.clone().to_string(),
+ dangling_comments.clone(),
+ );
+ dangling_comments.clear();
+ }
if let Some(plugin) = self.plugins.get(§ion_type) {
let id_schema =
plugin.get_id_schema().unwrap_or(self.id_schema);
@@ -461,8 +518,28 @@ impl SectionConfig {
}
}
ParseState::InsideSection(plugin, ref mut section_id, ref mut config) => {
+ if line.trim().starts_with('#') {
+ dangling_comments.push(line.to_string());
+ continue;
+ }
if line.trim().is_empty() {
// finish section
+ match result.comments.get(section_id) {
+ Some(e) => {
+ let mut comments = e.clone();
+ comments.append(&mut dangling_comments);
+ result.comments.insert(section_id.to_string(), comments);
+ }
+ None if !dangling_comments.is_empty() => {
+ result.comments.insert(
+ section_id.to_string(),
+ dangling_comments.clone(),
+ );
+ }
+ _ => (),
+ };
+ dangling_comments.clear();
+
test_required_properties(
config,
plugin.properties,
@@ -479,6 +556,13 @@ impl SectionConfig {
}
if let Some((key, value)) = (self.parse_section_content)(line) {
//println!("CONTENT: key: {} value: {}", key, value);
+ if !dangling_comments.is_empty() {
+ result.comments.insert(
+ format!("{}:{}", section_id, key),
+ dangling_comments.clone(),
+ );
+ dangling_comments.clear();
+ }
let schema = plugin.properties.lookup(&key);
let (is_array, prop_schema) = match schema {
@@ -522,6 +606,21 @@ impl SectionConfig {
) => {
if line.trim().is_empty() {
// finish section
+ match result.comments.get(section_id) {
+ Some(e) => {
+ let mut comments = e.clone();
+ comments.append(&mut dangling_comments);
+ result.comments.insert(section_id.to_string(), comments);
+ }
+ None if !dangling_comments.is_empty() => {
+ result.comments.insert(
+ section_id.to_string(),
+ dangling_comments.clone(),
+ );
+ }
+ _ => (),
+ };
+ dangling_comments.clear();
result.set_data(section_id, section_type, config.take())?;
result.record_order(section_id);
@@ -529,6 +628,13 @@ impl SectionConfig {
continue;
}
if let Some((key, value)) = (self.parse_section_content)(line) {
+ if !dangling_comments.is_empty() {
+ result.comments.insert(
+ format!("{}:{}", section_id, key),
+ dangling_comments.clone(),
+ );
+ dangling_comments.clear();
+ }
match &mut config[&key] {
Value::Null => config[key] = json!(value),
// Assume it's an array schema in order to handle actual array
@@ -836,8 +942,8 @@ lvmthin: local-lvm2
let res = config.parse(filename, raw);
println!("RES: {:?}", res);
- let raw = config.write(filename, &res.unwrap());
- println!("CONFIG:\n{}", raw.unwrap());
+ let raw_encoded = config.write(filename, &res.unwrap()).unwrap();
+ println!("CONFIG:\n{}", raw_encoded);
}
// cargo test test_section_config2 -- --nocapture
@@ -897,9 +1003,11 @@ fn test_section_config2() {
config.register_plugin(plugin);
let raw = r"
-
+# this is a comment
user: root@pam
+ # this is also a comment
email root@example.com
+ #email comment
group: mygroup
comment a very important group
@@ -1248,3 +1356,148 @@ pub fn dump_section_config(config: &SectionConfig) -> String {
res
}
+
+#[test]
+fn test_comments_error() {
+ const PROPERTIES: ObjectSchema = ObjectSchema::new(
+ "lvmthin properties",
+ &[(
+ "content",
+ true,
+ &StringSchema::new("Storage content types.").schema(),
+ )],
+ );
+
+ let plugin = SectionConfigPlugin::new("lvmthin".to_string(), None, &PROPERTIES);
+
+ const ID_SCHEMA: Schema = StringSchema::new("Storage ID schema.")
+ .min_length(3)
+ .schema();
+ let mut config = SectionConfig::new(&ID_SCHEMA);
+ config.register_plugin(plugin);
+
+ let raw = r"
+
+lvmthin: local-lvm
+ content rootdir,im#ages
+
+lvmthin: local-lvm2
+#lvmthin: local-lvm2
+ #comment
+ content r#ootdir,images
+";
+
+ let res = config.parse("test.cfg", raw).unwrap();
+ println!("RES: {:?}", res);
+
+ // we only allow full-line comments, so check if these don't change the behavior
+ assert_eq!(
+ res.sections
+ .get("local-lvm")
+ .unwrap()
+ .1
+ .get("content")
+ .unwrap(),
+ &serde_json::Value::String("rootdir,im#ages".to_string())
+ );
+ assert_eq!(
+ res.sections
+ .get("local-lvm2")
+ .unwrap()
+ .1
+ .get("content")
+ .unwrap(),
+ &serde_json::Value::String("r#ootdir,images".to_string())
+ );
+
+ let raw_encoded = config.write("test.cfg", &res).unwrap();
+ println!("CONFIG:\n{}", raw_encoded);
+}
+
+#[test]
+fn test_comments_delete_section() {
+ const PROPERTIES: ObjectSchema = ObjectSchema::new(
+ "lvmthin properties",
+ &[
+ (
+ "content",
+ true,
+ &StringSchema::new("Storage content types.").schema(),
+ ),
+ (
+ "test",
+ true,
+ &StringSchema::new("Storage content types.").schema(),
+ ),
+ ],
+ );
+
+ let plugin = SectionConfigPlugin::new("lvmthin".to_string(), None, &PROPERTIES);
+
+ const ID_SCHEMA: Schema = StringSchema::new("Storage ID schema.")
+ .min_length(3)
+ .schema();
+ let mut config = SectionConfig::new(&ID_SCHEMA);
+ config.register_plugin(plugin);
+
+ let raw = r"
+
+# cool local-lvm
+lvmthin: local-lvm
+ content rootdir,im#ages
+ test blah
+ # cool, but not in section
+# cool, but not in indented
+
+# lvmthin: local-lvm
+ # content rootdir,im#ages
+ # test blah
+
+lvmthin: local-lvm2
+#lvmthin: local-lvm2
+ #comment
+ content r#ootdir,images
+ # another one
+ test blah
+";
+
+ let mut res = config.parse("test.cfg", raw).unwrap();
+ println!("RES: {:?}", res);
+
+ // we only allow full-line comments, so check if these don't change the behavior
+ assert_eq!(
+ res.sections
+ .get("local-lvm")
+ .unwrap()
+ .1
+ .get("content")
+ .unwrap(),
+ &serde_json::Value::String("rootdir,im#ages".to_string())
+ );
+ assert_eq!(
+ res.sections
+ .get("local-lvm2")
+ .unwrap()
+ .1
+ .get("content")
+ .unwrap(),
+ &serde_json::Value::String("r#ootdir,images".to_string())
+ );
+
+ res.sections.remove("local-lvm");
+
+ // removing a single attribute
+ let mut new_section = res.sections.get("local-lvm2").unwrap().1.clone();
+ new_section.as_object_mut().unwrap().remove("test");
+ res.sections.insert(
+ "local-lvm2".to_string(),
+ ("lvmthin".to_string(), new_section),
+ );
+
+ let raw_encoded = config.write("test.cfg", &res).unwrap();
+ println!("CONFIG:\n{}", raw_encoded);
+ assert_eq!(raw_encoded.contains("# cool local-lvm"), false);
+ assert_eq!(raw_encoded.contains("# cool, but not in section"), false);
+ assert_eq!(raw_encoded.contains("# cool, but not in indented"), false);
+ assert_eq!(raw_encoded.contains("# another one"), true);
+}
--
2.39.2
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2023-08-25 10:56 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-25 10:55 [pbs-devel] [PATCH proxmox] close #4263: section-config: add comment support in section-config files Gabriel Goller
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal