3 use std
::collections
::HashMap
;
4 use std
::collections
::HashSet
;
5 use std
::collections
::VecDeque
;
7 use serde_json
::{json, Value}
;
10 use crate::api
::schema
::*;
12 pub struct SectionConfigPlugin
{
14 properties
: ObjectSchema
,
17 impl SectionConfigPlugin
{
19 pub fn new(type_name
: String
, properties
: ObjectSchema
) -> Self {
20 Self { type_name, properties }
25 pub struct SectionConfig
{
26 plugins
: HashMap
<String
, SectionConfigPlugin
>,
28 id_schema
: Arc
<Schema
>,
29 parse_section_header
: fn(&str) -> Option
<(String
, String
)>,
30 parse_section_content
: fn(&str) -> Option
<(String
, String
)>,
31 format_section_header
: fn(type_name
: &str, section_id
: &str, data
: &Value
) -> String
,
36 InsideSection(&'a SectionConfigPlugin
, String
, Value
),
40 pub struct SectionConfigData
{
41 pub sections
: HashMap
<String
, (String
, Value
)>,
42 order
: VecDeque
<String
>,
45 impl SectionConfigData
{
47 pub fn new() -> Self {
48 Self { sections: HashMap::new(), order: VecDeque::new() }
51 pub fn set_data(&mut self, section_id
: &str, type_name
: &str, config
: Value
) {
52 // fixme: verify section_id schema here??
53 self.sections
.insert(section_id
.to_string(), (type_name
.to_string(), config
));
56 fn record_order(&mut self, section_id
: &str) {
57 self.order
.push_back(section_id
.to_string());
60 pub fn convert_to_array(&self, id_prop
: &str) -> Value
{
61 let mut list
: Vec
<Value
> = vec
![];
63 for (section_id
, (_
, data
)) in &self.sections
{
64 let mut item
= data
.clone();
65 item
.as_object_mut().unwrap().insert(id_prop
.into(), section_id
.clone().into());
75 pub fn new(id_schema
: Arc
<Schema
>) -> Self {
77 plugins
: HashMap
::new(),
79 parse_section_header
: SectionConfig
::default_parse_section_header
,
80 parse_section_content
: SectionConfig
::default_parse_section_content
,
81 format_section_header
: SectionConfig
::default_format_section_header
,
85 pub fn register_plugin(&mut self, plugin
: SectionConfigPlugin
) {
86 self.plugins
.insert(plugin
.type_name
.clone(), plugin
);
89 pub fn write(&self, _filename
: &str, config
: &SectionConfigData
) -> Result
<String
, Error
> {
91 let mut list
= VecDeque
::new();
93 let mut done
= HashSet
::new();
95 for section_id
in &config
.order
{
96 if config
.sections
.get(section_id
) == None { continue }
;
97 list
.push_back(section_id
);
98 done
.insert(section_id
);
101 for (section_id
, _
) in &config
.sections
{
102 if done
.contains(section_id
) { continue }
;
103 list
.push_back(section_id
);
106 let mut raw
= String
::new();
108 for section_id
in list
{
109 let (type_name
, section_config
) = config
.sections
.get(section_id
).unwrap();
110 let plugin
= self.plugins
.get(type_name
).unwrap();
112 if let Err(err
) = parse_simple_value(§ion_id
, &self.id_schema
) {
113 bail
!("syntax error in section identifier: {}", err
.to_string());
116 verify_json_object(section_config
, &plugin
.properties
)?
;
117 println
!("REAL WRITE {} {} {:?}\n", section_id
, type_name
, section_config
);
119 let head
= (self.format_section_header
)(type_name
, section_id
, section_config
);
121 if !raw
.is_empty() { raw += "\n" }
125 for (key
, value
) in section_config
.as_object().unwrap() {
126 let text
= match value
{
127 Value
::Null
=> { continue; }
, // do nothing (delete)
128 Value
::Bool(v
) => v
.to_string(),
129 Value
::String(v
) => v
.to_string(),
130 Value
::Number(v
) => v
.to_string(),
132 bail
!("got unsupported type in section '{}' key '{}'", section_id
, key
);
146 pub fn parse(&self, filename
: &str, raw
: &str) -> Result
<SectionConfigData
, Error
> {
148 let mut state
= ParseState
::BeforeHeader
;
150 let test_required_properties
= |value
: &Value
, schema
: &ObjectSchema
| -> Result
<(), Error
> {
151 for (name
, (optional
, _prop_schema
)) in &schema
.properties
{
152 if *optional
== false && value
[name
] == Value
::Null
{
153 return Err(format_err
!("property '{}' is missing and it is not optional.", name
));
163 let mut result
= SectionConfigData
::new();
165 let mut create_section
= |section_id
: &str, type_name
: &str, config
| {
166 result
.set_data(section_id
, type_name
, config
);
167 result
.record_order(section_id
);
171 for line
in raw
.lines() {
176 ParseState
::BeforeHeader
=> {
178 if line
.trim().is_empty() { continue; }
180 if let Some((section_type
, section_id
)) = (self.parse_section_header
)(line
) {
181 //println!("OKLINE: type: {} ID: {}", section_type, section_id);
182 if let Some(ref plugin
) = self.plugins
.get(§ion_type
) {
183 if let Err(err
) = parse_simple_value(§ion_id
, &self.id_schema
) {
184 bail
!("syntax error in section identifier: {}", err
.to_string());
186 state
= ParseState
::InsideSection(plugin
, section_id
, json
!({}
));
188 bail
!("unknown section type '{}'", section_type
);
191 bail
!("syntax error (expected header)");
194 ParseState
::InsideSection(plugin
, ref mut section_id
, ref mut config
) => {
196 if line
.trim().is_empty() {
198 test_required_properties(config
, &plugin
.properties
)?
;
199 create_section(section_id
, &plugin
.type_name
, config
.take());
200 state
= ParseState
::BeforeHeader
;
203 if let Some((key
, value
)) = (self.parse_section_content
)(line
) {
204 //println!("CONTENT: key: {} value: {}", key, value);
206 if let Some((_optional
, prop_schema
)) = plugin
.properties
.properties
.get
::<str>(&key
) {
207 match parse_simple_value(&value
, prop_schema
) {
209 if config
[&key
] == Value
::Null
{
212 bail
!("duplicate property '{}'", key
);
216 bail
!("property '{}': {}", key
, err
.to_string());
220 bail
!("unknown property '{}'", key
)
223 bail
!("syntax error (expected section properties)");
229 if let ParseState
::InsideSection(plugin
, section_id
, config
) = state
{
231 test_required_properties(&config
, &plugin
.properties
)?
;
232 create_section(§ion_id
, &plugin
.type_name
, config
);
237 }).map_err(|e
| format_err
!("line {} - {}", line_no
, e
))?
;
241 }).map_err(|e
: Error
| format_err
!("parsing '{}' failed: {}", filename
, e
))
244 pub fn default_format_section_header(type_name
: &str, section_id
: &str, _data
: &Value
) -> String
{
245 return format
!("{}: {}\n", type_name
, section_id
);
248 pub fn default_parse_section_content(line
: &str) -> Option
<(String
, String
)> {
250 if line
.is_empty() { return None; }
251 let first_char
= line
.chars().next().unwrap();
253 if !first_char
.is_whitespace() { return None }
255 let mut kv_iter
= line
.trim_left().splitn(2, |c
: char| c
.is_whitespace());
257 let key
= match kv_iter
.next() {
262 if key
.len() == 0 { return None; }
264 let value
= match kv_iter
.next() {
269 Some((key
.into(), value
.into()))
272 pub fn default_parse_section_header(line
: &str) -> Option
<(String
, String
)> {
274 if line
.is_empty() { return None; }
;
276 let first_char
= line
.chars().next().unwrap();
278 if !first_char
.is_alphabetic() { return None }
280 let mut head_iter
= line
.splitn(2, '
:'
);
282 let section_type
= match head_iter
.next() {
287 if section_type
.len() == 0 { return None; }
289 let section_id
= match head_iter
.next() {
294 Some((section_type
.into(), section_id
.into()))
299 // cargo test test_section_config1 -- --nocapture
301 fn test_section_config1() {
303 let filename
= "storage.cfg";
305 //let mut file = File::open(filename).expect("file not found");
306 //let mut contents = String::new();
307 //file.read_to_string(&mut contents).unwrap();
309 let plugin
= SectionConfigPlugin
::new(
310 "lvmthin".to_string(),
311 ObjectSchema
::new("lvmthin properties")
312 .required("thinpool", StringSchema
::new("LVM thin pool name."))
313 .required("vgname", StringSchema
::new("LVM volume group name."))
314 .optional("content", StringSchema
::new("Storage content types."))
317 let id_schema
= StringSchema
::new("Storage ID schema.")
321 let mut config
= SectionConfig
::new(id_schema
);
322 config
.register_plugin(plugin
);
329 content rootdir,images
334 content rootdir,images
337 let res
= config
.parse(filename
, &raw
);
338 println
!("RES: {:?}", res
);
339 let raw
= config
.write(filename
, &res
.unwrap());
340 println
!("CONFIG:\n{}", raw
.unwrap());