diff --git a/Tools/langtool/Cargo.toml b/Tools/langtool/Cargo.toml index ad24cc8bb0..8a980f46a5 100644 --- a/Tools/langtool/Cargo.toml +++ b/Tools/langtool/Cargo.toml @@ -1,9 +1,8 @@ [package] -name = "langtool" -version = "0.1.0" authors = ["Henrik RydgÄrd "] edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "langtool" +version = "0.1.0" [dependencies] +#structopt = "0.3" diff --git a/Tools/langtool/src/inifile.rs b/Tools/langtool/src/inifile.rs new file mode 100644 index 0000000000..8669175c9b --- /dev/null +++ b/Tools/langtool/src/inifile.rs @@ -0,0 +1,136 @@ +use std::path::{Path, PathBuf}; +use std::io::{self, Write}; +use std::fs::File; + +use crate::section::Section; + +#[derive(Debug)] +pub struct IniFile { + pub filename: PathBuf, + pub preamble: Vec, + pub sections: Vec
, + pub has_bom: bool, +} + +impl IniFile { + pub fn parse(filename: &str) -> io::Result { + let lines = read_lines(filename)?; + + let mut sections = vec![]; + let mut preamble = vec![]; + let mut cur_section = None; + + let mut has_bom = false; + + for line in lines { + let line = line.unwrap(); + + let line = if let Some(line) = line.strip_prefix('\u{feff}') { + has_bom = true; + line + } else { + &line + }; + + if let Some('[') = line.chars().next() { + if let Some(right_bracket) = line.find(']') { + if let Some(section) = cur_section.take() { + sections.push(section); + } + + let name = &line[1..right_bracket]; + cur_section = Some(Section { + name: name.to_owned(), + title_line: line.to_owned(), // preserves comment and bom + lines: vec![], + }); + } else { + // Bad syntax + break; + } + } else if let Some(cur_section) = &mut cur_section { + cur_section.lines.push(line.to_owned()); + } else { + preamble.push(line.to_owned()); + } + } + + if let Some(section) = cur_section.take() { + sections.push(section); + } + + let ini = IniFile { + filename: PathBuf::from(filename), + preamble, + sections, + has_bom, + }; + Ok(ini) + } + + pub fn write(&self) -> io::Result<()> { + let file = std::fs::File::create(&self.filename)?; + let mut file = std::io::LineWriter::new(file); + + // Write BOM + if self.has_bom { + file.write_all("\u{feff}".as_bytes())?; + } + for line in &self.preamble { + file.write_all(line.as_bytes())?; + file.write_all(b"\n")?; + } + for section in &self.sections { + file.write_all(section.title_line.as_bytes())?; + file.write_all(b"\n")?; + for line in §ion.lines { + file.write_all(line.as_bytes())?; + file.write_all(b"\n")?; + } + } + + Ok(()) + } + + // Assumes alphabetical section order! + pub fn insert_section_if_missing(&mut self, section: &Section) -> bool { + // First, check if it's there. + + for iter_section in &self.sections { + if iter_section.name == section.name { + return false; + } + } + + // Then, find a suitable insertion spot + for (i, iter_section) in self.sections.iter_mut().enumerate() { + if iter_section.name > section.name { + println!("Inserting section {}", section.name); + self.sections.insert(i, section.clone()); + return true; + } + } + // Reached the end for some reason? Add it. + self.sections.push(section.clone()); + true + } + + pub fn get_section_mut(&mut self, section_name: &str) -> Option<&mut Section> { + for section in &mut self.sections { + if section.name == section_name { + return Some(section); + } + } + None + } +} + +// Grabbed from a sample, a fast line reader iterator. +fn read_lines

(filename: P) -> io::Result>> +where + P: AsRef, +{ + let file = File::open(filename)?; + use std::io::BufRead; + Ok(io::BufReader::new(file).lines()) +} diff --git a/Tools/langtool/src/main.rs b/Tools/langtool/src/main.rs index a33adf1c7a..5f9ab0b861 100644 --- a/Tools/langtool/src/main.rs +++ b/Tools/langtool/src/main.rs @@ -1,240 +1,9 @@ -use std::fs::File; -use std::io::{self, BufRead, Write}; -use std::path::{Path, PathBuf}; +use std::io; -// Super simplified ini file processor. -// Doesn't even bother with understanding comments. -// Just understands section headings and -// keys and values, split by ' = '. +mod section; -#[derive(Debug, Clone)] -struct Section { - name: String, - title_line: String, - lines: Vec, -} - -impl Section { - fn insert_line_if_missing(&mut self, line: &str) -> bool { - let prefix = if let Some(pos) = line.find(" =") { - &line[0..pos + 2] - } else { - return false; - }; - // Ignore comments when copying lines. - if prefix.starts_with('#') { - return false; - } - // Need to decide a policy for these. - if prefix.starts_with("translators") { - return false; - } - let prefix = prefix.to_owned(); - - for iter_line in &self.lines { - if iter_line.starts_with(&prefix) { - // Already have it - return false; - } - } - - // Now try to insert it at an alphabetic-ish location. - let prefix = prefix.to_ascii_lowercase(); - - // Then, find a suitable insertion spot - for (i, iter_line) in self.lines.iter().enumerate() { - if iter_line.to_ascii_lowercase() > prefix { - println!("Inserting line {} into {}", line, self.name); - self.lines.insert(i, line.to_owned()); - return true; - } - } - - for i in (0..self.lines.len()).rev() { - if self.lines[i].is_empty() { - continue; - } - println!("Inserting line {} into {}", line, self.name); - self.lines.insert(i + 1, line.to_owned()); - return true; - } - - println!("failed to insert {}", line); - true - } - - fn comment_out_lines_if_not_in(&mut self, other: &Section) { - // Brute force (O(n^2)). Bad but not a problem. - - for line in &mut self.lines { - let prefix = if let Some(pos) = line.find(" =") { - &line[0..pos + 2] - } else { - // Keep non-key lines. - continue; - }; - if prefix.starts_with("Font") || prefix.starts_with('#') { - continue; - } - if !other.lines.iter().any(|line| line.starts_with(prefix)) { - println!("Commenting out: {}", line); - // Comment out the line. - *line = "#".to_owned() + line; - } - } - } - - fn remove_lines_if_not_in(&mut self, other: &Section) { - // Brute force (O(n^2)). Bad but not a problem. - - self.lines.retain(|line| { - let prefix = if let Some(pos) = line.find(" =") { - &line[0..pos + 2] - } else { - // Keep non-key lines. - return true; - }; - if prefix.starts_with("Font") || prefix.starts_with('#') { - return true; - } - if !other.lines.iter().any(|line| line.starts_with(prefix)) { - false - } else { - true - } - }); - } -} - -#[derive(Debug)] -struct IniFile { - filename: PathBuf, - preamble: Vec, - sections: Vec

, - has_bom: bool, -} - -impl IniFile { - fn parse(filename: &str) -> io::Result { - let lines = read_lines(filename)?; - - let mut sections = vec![]; - let mut preamble = vec![]; - let mut cur_section = None; - - let mut has_bom = false; - - for line in lines { - let line = line.unwrap(); - - let line = if let Some(line) = line.strip_prefix('\u{feff}') { - has_bom = true; - line - } else { - &line - }; - - if let Some('[') = line.chars().next() { - if let Some(right_bracket) = line.find(']') { - if let Some(section) = cur_section.take() { - sections.push(section); - } - - let name = &line[1..right_bracket]; - cur_section = Some(Section { - name: name.to_owned(), - title_line: line.to_owned(), // preserves comment and bom - lines: vec![], - }); - } else { - // Bad syntax - break; - } - } else if let Some(cur_section) = &mut cur_section { - cur_section.lines.push(line.to_owned()); - } else { - preamble.push(line.to_owned()); - } - } - - if let Some(section) = cur_section.take() { - sections.push(section); - } - - let ini = IniFile { - filename: PathBuf::from(filename), - preamble, - sections, - has_bom, - }; - Ok(ini) - } - - fn write(&self) -> io::Result<()> { - let file = std::fs::File::create(&self.filename)?; - let mut file = std::io::LineWriter::new(file); - - // Write BOM - if self.has_bom { - file.write_all("\u{feff}".as_bytes())?; - } - for line in &self.preamble { - file.write_all(line.as_bytes())?; - file.write_all(b"\n")?; - } - for section in &self.sections { - file.write_all(section.title_line.as_bytes())?; - file.write_all(b"\n")?; - for line in §ion.lines { - file.write_all(line.as_bytes())?; - file.write_all(b"\n")?; - } - } - - Ok(()) - } - - // Assumes alphabetical section order! - fn insert_section_if_missing(&mut self, section: &Section) -> bool { - // First, check if it's there. - - for iter_section in &self.sections { - if iter_section.name == section.name { - return false; - } - } - - // Then, find a suitable insertion spot - for (i, iter_section) in self.sections.iter_mut().enumerate() { - if iter_section.name > section.name { - println!("Inserting section {}", section.name); - self.sections.insert(i, section.clone()); - return true; - } - } - // Reached the end for some reason? Add it. - self.sections.push(section.clone()); - true - } - - fn get_section_mut(&mut self, section_name: &str) -> Option<&mut Section> { - for section in &mut self.sections { - if section.name == section_name { - return Some(section); - } - } - None - } -} - -// Grabbed from a sample, a fast line reader iterator. -fn read_lines

(filename: P) -> io::Result>> -where - P: AsRef, -{ - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) -} +mod inifile; +use inifile::IniFile; fn copy_missing_lines(reference_ini: &IniFile, target_ini: &mut IniFile) -> io::Result<()> { // Insert any missing full sections. diff --git a/Tools/langtool/src/section.rs b/Tools/langtool/src/section.rs new file mode 100644 index 0000000000..32de98b328 --- /dev/null +++ b/Tools/langtool/src/section.rs @@ -0,0 +1,104 @@ + +// Super simplified ini file processor. +// Doesn't even bother with understanding comments. +// Just understands section headings and +// keys and values, split by ' = '. + +#[derive(Debug, Clone)] +pub struct Section { + pub name: String, + pub title_line: String, + pub lines: Vec, +} + +impl Section { + pub fn insert_line_if_missing(&mut self, line: &str) -> bool { + let prefix = if let Some(pos) = line.find(" =") { + &line[0..pos + 2] + } else { + return false; + }; + // Ignore comments when copying lines. + if prefix.starts_with('#') { + return false; + } + // Need to decide a policy for these. + if prefix.starts_with("translators") { + return false; + } + let prefix = prefix.to_owned(); + + for iter_line in &self.lines { + if iter_line.starts_with(&prefix) { + // Already have it + return false; + } + } + + // Now try to insert it at an alphabetic-ish location. + let prefix = prefix.to_ascii_lowercase(); + + // Then, find a suitable insertion spot + for (i, iter_line) in self.lines.iter().enumerate() { + if iter_line.to_ascii_lowercase() > prefix { + println!("Inserting line {} into {}", line, self.name); + self.lines.insert(i, line.to_owned()); + return true; + } + } + + for i in (0..self.lines.len()).rev() { + if self.lines[i].is_empty() { + continue; + } + println!("Inserting line {} into {}", line, self.name); + self.lines.insert(i + 1, line.to_owned()); + return true; + } + + println!("failed to insert {}", line); + true + } + + pub fn comment_out_lines_if_not_in(&mut self, other: &Section) { + // Brute force (O(n^2)). Bad but not a problem. + + for line in &mut self.lines { + let prefix = if let Some(pos) = line.find(" =") { + &line[0..pos + 2] + } else { + // Keep non-key lines. + continue; + }; + if prefix.starts_with("Font") || prefix.starts_with('#') { + continue; + } + if !other.lines.iter().any(|line| line.starts_with(prefix)) { + println!("Commenting out: {}", line); + // Comment out the line. + *line = "#".to_owned() + line; + } + } + } + + pub fn remove_lines_if_not_in(&mut self, other: &Section) { + // Brute force (O(n^2)). Bad but not a problem. + + self.lines.retain(|line| { + let prefix = if let Some(pos) = line.find(" =") { + &line[0..pos + 2] + } else { + // Keep non-key lines. + return true; + }; + if prefix.starts_with("Font") || prefix.starts_with('#') { + return true; + } + if !other.lines.iter().any(|line| line.starts_with(prefix)) { + false + } else { + true + } + }); + } +} \ No newline at end of file