--- /dev/null
+//! Copyright (C) 2023 Gustav Eek <gustav.eek@fripost.org>
+//!
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+use std::io; // bring flush() into scope with `use std::io::Write`
+use std::io::prelude::*; // Bring `std::io::BufRead` in scope
+
+// Statrs, `don't use statrs::prec` and
+// `statrs::statistics::Distribution` from examples
+use statrs::distribution::{
+ Exp, LogNormal, ContinuousCDF}; // Continuous needed for pdf
+
+use clap::Parser; // CLI Parser, replaces need for use `std::env`
+use regex::Regex;
+
+pub const DEBUG: bool = false;
+
+fn winnow(mut list: Vec<String>) -> (Conf, Vec<String> ) {
+
+ list.retain(|x| x != ""); // only keep nonempty
+
+ // Search patterns. Find pipe tables as a bullet item
+
+ let bullet = Regex::new(r"^(\s*[\*|-]\s*)").unwrap();
+ let number = Regex::new(r"^(\s*)([0-9]+)([).]\s*)").unwrap();
+ let prio = Regex::new(r"^\s*[0-9]+\s*%[|\s]*").unwrap();
+
+ if DEBUG { eprintln!( // debug patterns
+ "Matches first item: '{}', '{}', '{}'",
+ match bullet.captures(&list[0]){
+ None => "",
+ Some(x) => x.get(0).unwrap().as_str()},
+ match number.captures(&list[0]){
+ None => "",
+ Some(x) => x.get(0).unwrap().as_str()},
+ match prio.captures(&list[0]){
+ None => "",
+ Some(x) => x.get(0).unwrap().as_str()});}
+
+ // Use index of the longest relevant group match to create conf
+ // template. Call this "the rule of index of the longest match".
+
+ let maxi_bullet = list.iter().map(
+ |y| match bullet.captures(y) {
+ None => 0,
+ Some(x) => x.get(1).unwrap().as_str().len(),
+ }).enumerate().fold(
+ (0, 0), |max, (ind, val)| if val > max.1 {(ind, val)} else {max}).0;
+
+ let maxi_number = list.iter().map(
+ |y| match number.captures(y) {
+ None => 0,
+ Some(x) => x.get(2).unwrap().as_str().len(),
+ }).enumerate().fold(
+ (0, 0), |max, (ind, val)| if val > max.1 {(ind, val)} else {max}).0;
+
+ if DEBUG { eprintln!( // debug longest match
+ "Longest match: {}, '{}'; {}, '{}'",
+ maxi_bullet,
+ match bullet.captures(&list[maxi_bullet]) {
+ None => "",
+ Some(x) => x.get(1).unwrap().as_str()},
+ maxi_number,
+ match number.captures(&list[maxi_number]) {
+ None => "",
+ Some(x) => x.get(2).unwrap().as_str()})};
+
+ let conf = Conf {
+ bullet: match bullet.captures(&list[maxi_bullet]) {
+ None => "".to_string(),
+ Some(x) => x.get(1).unwrap().as_str().to_string(),
+ },
+ number: match number.captures(&list[maxi_number]) {
+ None => "".to_string(),
+ Some(x) => format!(
+ "{}{{}}{}",
+ x.get(1).unwrap().as_str(),
+ x.get(3).unwrap().as_str())
+ },
+ };
+
+ if DEBUG {
+ eprintln!("Conf: {:?}", conf);
+ }
+
+ // Remove patterns and trim
+
+ for l in &mut *list {
+ *l = bullet.replace_all(&l, "").to_string();
+ *l = number.replace_all(&l, "").to_string();
+ *l = prio.replace_all(&l, "").to_string();
+ *l = l.trim().to_owned();
+ }
+ list.retain(|x| x != ""); // again
+ return (conf, list)
+}
+
+pub fn input() -> (Conf, Vec<String>) {
+
+ let stdin = io::stdin();
+ let list: Vec<String> =
+ stdin.lock().lines()
+ .map(|x| x.unwrap())
+ .collect();
+ return winnow(list)
+}
+
+pub fn output(conf: &Conf, prio: &Vec<f64>, ranked: &Vec<String>) {
+
+ let digs: usize = (prio.len() as f32).log(10.0).ceil() as usize;
+
+ for (i, (p, l)) in prio.iter().zip(ranked).enumerate() {
+
+ let pre = if conf.number != "" {
+ conf.number.replace("{}", format!( // Replace template due
+ "{:digs$}", i + 1).as_str()) // to smt::fmt
+ } else {
+ conf.bullet.clone()
+ };
+ let pst = if conf.bullet.contains("|") {" | "} else {"\t"};
+
+ println!("{}{:2.0} %{}{}", pre, 100.0 * p, pst, l)
+ }
+}
+
+fn normalize(mut arg: Vec<f64>) -> Vec<f64> {
+
+ let mut sum = 0.0;
+ for v in &arg {
+ sum += v;
+ }
+ for i in 0..arg.len() {
+ arg[i] /= sum;
+ }
+ arg
+}
+
+pub fn delta(n: i32) -> Vec<f64> {
+
+ const NAME: &str = "Delta";
+ const MEAN: f64 = 1.0; // unessential thanks to normalization
+ let mut prio: Vec<f64> = Vec::new();
+ if DEBUG { eprint!("{}: ", NAME) }
+ for i in 1..n + 1 {
+ prio.push(MEAN);
+ if DEBUG {
+ eprint!("i = {}, x = {:.2}; ", i, MEAN);
+ }
+ }
+ if DEBUG { eprintln!("\x08\x08."); }
+ prio.reverse();
+ normalize(prio)
+}
+
+pub fn exp(n: i32) -> Vec<f64> {
+
+ const NAME: &str = "Exp";
+ const RATE: f64 = 1.0; // rate is unessential thanks to normalization
+ let dist = Exp::new(RATE).unwrap();
+ let mut prio: Vec<f64> = Vec::new();
+ if DEBUG { eprint!("{}: ", NAME) }
+ for i in 1..n + 1 {
+ let f = i as f64 / (n as f64 + 1.0);
+ let x = dist.inverse_cdf(f);
+ if DEBUG {
+ eprint!("i = {}, f = {:.2}, x = {:.2}; ", i, f, x);
+ }
+ prio.push(x);
+ }
+ if DEBUG { eprintln!("\x08\x08."); }
+ prio.reverse();
+ normalize(prio)
+}
+
+pub fn lognormal(n: i32, std: f64) -> Vec<f64> {
+
+ const NAME: &str = "Lognormal";
+ const MEAN: f64 = 0.0; // mean is unessential thanks to
+ // normalization, and std of N
+ let dist = LogNormal::new(MEAN, std).unwrap();
+ let mut prio: Vec<f64> = Vec::new();
+ if DEBUG { eprint!("{}: ", NAME) }
+ for i in 1..n + 1 {
+ let f = i as f64 / (n as f64 + 1.0);
+ let x = dist.inverse_cdf(f);
+ if DEBUG {
+ eprint!("i = {}, f = {:.2}, x = {:.2}; ", i, f, x);
+ }
+ prio.push(x);
+ }
+ if DEBUG { eprintln!("\x08\x08."); }
+ prio.reverse();
+ normalize(prio)
+}
+
+#[derive(Debug)]
+
+pub struct Conf {
+ bullet: String,
+ number: String,
+}
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+
+/// Rank N' Auto, automatic normalized ranking
+
+pub struct Args {
+
+ /// Ranking distribution
+ #[arg(short, default_value_t = String::from("lognormal"))]
+ pub distribution: String,
+
+ /// Skewness of the ranking
+ #[arg(short)]
+ pub skewness: Option<String>,
+}
+
+
+#[test]
+
+fn ordinary_input() {
+
+ let mut arg: Vec<String> = Vec::new();
+ let mut exp: Vec<String> = Vec::new();
+ for s in vec!["", "", "Hej ☕ glade", "", "Amatör", "", ""] {
+ arg.push(String::from(s));
+ }
+ for s in vec!["Hej ☕ glade", "Amatör"] {
+ exp.push(String::from(s));
+ }
+ let (_, res) = winnow(arg);
+ assert_eq!(exp, res);
+}
+
+#[test]
+
+fn whitespace() {
+
+ let arg: Vec<String> =
+ "\n\nHej du\t\n glade\nta en\n\n" // Convert &str to String
+ .split("\n").map(|x| x.to_owned()) // with `.to_owned()` or
+ .collect(); // `.to_string()`
+ let exp: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ let (_, res) = winnow(arg);
+ assert_eq!(exp, res);
+}
+
+#[test]
+
+fn bullets() {
+
+ let arg: Vec<String> =
+ "\n * Hej du\t\n - glade\n-\tta en\n\n"
+ .split("\n").map(|x| x.to_owned())
+ .collect();
+ let exp: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ let (conf, res) = winnow(arg);
+ assert_eq!(exp, res);
+ assert_eq!(conf.bullet, " - ".to_owned());
+ assert_eq!(conf.number, "".to_owned());
+}
+
+#[test]
+
+fn numbers() {
+
+ let arg: Vec<String> =
+ "1) Hej du\n 459. glade\n 2)ta en\n"
+ .split("\n").map(|x| x.to_owned())
+ .collect();
+ let exp: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ let (conf, res) = winnow(arg);
+ assert_eq!(exp, res);
+ assert_eq!(conf.bullet, "".to_owned());
+ assert_eq!(conf.number, " {}. ".to_owned());
+}
+
+#[test]
+
+fn prioinput() {
+
+ let arg: Vec<String> =
+ " - 70 % Hej du\n - 30 %\tglade\nta en\n"
+ .split("\n").map(|x| x.to_owned())
+ .collect();
+ let exp: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ let (_, res) = winnow(arg);
+ assert_eq!(exp, res);
+}
+
+#[test]
+
+fn pipe() {
+
+ let arg: Vec<String> =
+ "\n| Hej du\n | glade\n| ta en\n"
+ .split("\n").map(|x| x.to_owned())
+ .collect();
+ let exp: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ let (conf, res) = winnow(arg);
+ assert_eq!(exp, res);
+ assert_eq!(conf.bullet, " | ".to_owned());
+ assert_eq!(conf.number, "".to_owned());
+}
+
+#[test]
+
+fn bullout() {
+
+ let conf = Conf {
+ bullet: " * ".to_owned(),
+ number: "".to_owned()};
+ let prio = vec![0.6, 0.3, 0.1];
+ let ranked: Vec<String> =
+ "Hej du\nglade\nta en"
+ .split("\n").map(|x| x.to_owned()).collect();
+ output(&conf, &prio, &ranked);
+}
//!
//! SPDX-License-Identifier: GPL-3.0-or-later
-use std::io; // bring flush() into scope with `use std::io::Write`
-use std::io::prelude::*; // Bring `std::io::BufRead` in scope
+use clap::Parser;
-// Statrs, `don't use statrs::prec` and
-// `statrs::statistics::Distribution` from examples
-use statrs::distribution::{
- Exp, LogNormal, ContinuousCDF}; // Continuous needed for pdf
-
-use clap::Parser; // CLI Parser, replaces need for use `std::env`
-use regex::Regex;
-
-const DEBUG: bool = false;
-
-fn winnow(mut list: Vec<String>) -> (Conf, Vec<String> ) {
-
- list.retain(|x| x != ""); // only keep nonempty
-
- // Search patterns. Find pipe tables as a bullet item
-
- let bullet = Regex::new(r"^(\s*[\*|-]\s*)").unwrap();
- let number = Regex::new(r"^(\s*)([0-9]+)([).]\s*)").unwrap();
- let prio = Regex::new(r"^\s*[0-9]+\s*%[|\s]*").unwrap();
-
- if DEBUG { eprintln!( // debug patterns
- "Matches first item: '{}', '{}', '{}'",
- match bullet.captures(&list[0]){
- None => "",
- Some(x) => x.get(0).unwrap().as_str()},
- match number.captures(&list[0]){
- None => "",
- Some(x) => x.get(0).unwrap().as_str()},
- match prio.captures(&list[0]){
- None => "",
- Some(x) => x.get(0).unwrap().as_str()});}
-
- // Use index of the longest relevant group match to create conf
- // template. Call this "the rule of index of the longest match".
-
- let maxi_bullet = list.iter().map(
- |y| match bullet.captures(y) {
- None => 0,
- Some(x) => x.get(1).unwrap().as_str().len(),
- }).enumerate().fold(
- (0, 0), |max, (ind, val)| if val > max.1 {(ind, val)} else {max}).0;
-
- let maxi_number = list.iter().map(
- |y| match number.captures(y) {
- None => 0,
- Some(x) => x.get(2).unwrap().as_str().len(),
- }).enumerate().fold(
- (0, 0), |max, (ind, val)| if val > max.1 {(ind, val)} else {max}).0;
-
- if DEBUG { eprintln!( // debug longest match
- "Longest match: {}, '{}'; {}, '{}'",
- maxi_bullet,
- match bullet.captures(&list[maxi_bullet]) {
- None => "",
- Some(x) => x.get(1).unwrap().as_str()},
- maxi_number,
- match number.captures(&list[maxi_number]) {
- None => "",
- Some(x) => x.get(2).unwrap().as_str()})};
-
- let conf = Conf {
- bullet: match bullet.captures(&list[maxi_bullet]) {
- None => "".to_string(),
- Some(x) => x.get(1).unwrap().as_str().to_string(),
- },
- number: match number.captures(&list[maxi_number]) {
- None => "".to_string(),
- Some(x) => format!(
- "{}{{}}{}",
- x.get(1).unwrap().as_str(),
- x.get(3).unwrap().as_str())
- },
- };
-
- if DEBUG {
- eprintln!("Conf: {:?}", conf);
- }
-
- // Remove patterns and trim
-
- for l in &mut *list {
- *l = bullet.replace_all(&l, "").to_string();
- *l = number.replace_all(&l, "").to_string();
- *l = prio.replace_all(&l, "").to_string();
- *l = l.trim().to_owned();
- }
- list.retain(|x| x != ""); // again
- return (conf, list)
-}
-
-fn input() -> (Conf, Vec<String>) {
-
- let stdin = io::stdin();
- let list: Vec<String> =
- stdin.lock().lines()
- .map(|x| x.unwrap())
- .collect();
- return winnow(list)
-}
-
-fn output(conf: &Conf, prio: &Vec<f64>, ranked: &Vec<String>) {
-
- let digs: usize = (prio.len() as f32).log(10.0).ceil() as usize;
-
- for (i, (p, l)) in prio.iter().zip(ranked).enumerate() {
-
- let pre = if conf.number != "" {
- conf.number.replace("{}", format!( // Replace template due
- "{:digs$}", i + 1).as_str()) // to smt::fmt
- } else {
- conf.bullet.clone()
- };
- let pst = if conf.bullet.contains("|") {" | "} else {"\t"};
-
- println!("{}{:2.0} %{}{}", pre, 100.0 * p, pst, l)
- }
-}
-
-fn normalize(mut arg: Vec<f64>) -> Vec<f64> {
-
- let mut sum = 0.0;
- for v in &arg {
- sum += v;
- }
- for i in 0..arg.len() {
- arg[i] /= sum;
- }
- arg
-}
-
-fn delta(n: i32) -> Vec<f64> {
-
- const NAME: &str = "Delta";
- const MEAN: f64 = 1.0; // unessential thanks to normalization
- let mut prio: Vec<f64> = Vec::new();
- if DEBUG { eprint!("{}: ", NAME) }
- for i in 1..n + 1 {
- prio.push(MEAN);
- if DEBUG {
- eprint!("i = {}, x = {:.2}; ", i, MEAN);
- }
- }
- if DEBUG { eprintln!("\x08\x08."); }
- prio.reverse();
- normalize(prio)
-}
-
-fn exp(n: i32) -> Vec<f64> {
-
- const NAME: &str = "Exp";
- const RATE: f64 = 1.0; // rate is unessential thanks to normalization
- let dist = Exp::new(RATE).unwrap();
- let mut prio: Vec<f64> = Vec::new();
- if DEBUG { eprint!("{}: ", NAME) }
- for i in 1..n + 1 {
- let f = i as f64 / (n as f64 + 1.0);
- let x = dist.inverse_cdf(f);
- if DEBUG {
- eprint!("i = {}, f = {:.2}, x = {:.2}; ", i, f, x);
- }
- prio.push(x);
- }
- if DEBUG { eprintln!("\x08\x08."); }
- prio.reverse();
- normalize(prio)
-}
-
-fn lognormal(n: i32, std: f64) -> Vec<f64> {
-
- const NAME: &str = "Lognormal";
- const MEAN: f64 = 0.0; // mean is unessential thanks to
- // normalization, and std of N
- let dist = LogNormal::new(MEAN, std).unwrap();
- let mut prio: Vec<f64> = Vec::new();
- if DEBUG { eprint!("{}: ", NAME) }
- for i in 1..n + 1 {
- let f = i as f64 / (n as f64 + 1.0);
- let x = dist.inverse_cdf(f);
- if DEBUG {
- eprint!("i = {}, f = {:.2}, x = {:.2}; ", i, f, x);
- }
- prio.push(x);
- }
- if DEBUG { eprintln!("\x08\x08."); }
- prio.reverse();
- normalize(prio)
-}
-
-#[derive(Debug)]
-
-struct Conf {
- bullet: String,
- number: String,
-}
-
-#[derive(Parser, Debug)]
-#[command(author, version, about, long_about = None)]
-
-/// Rank N' Auto, automatic normalized ranking
-
-struct Args {
-
- /// Ranking distribution
- #[arg(short, default_value_t = String::from("lognormal"))]
- distribution: String,
-
- /// Skewness of the ranking
- #[arg(short)]
- skewness: Option<String>,
-}
+use ranknauto::{
+ DEBUG, // flag
+ input, output, // I/O
+ delta, exp, lognormal, // distributions
+ Args, // parsing
+};
fn main() {
else if a == "moderate" {1.0}
else if a == "heavy" {2.0}
else {1.0},
+
};
if DEBUG { eprintln!("Finally a skewness: {}", skew) }
output(&conf, &prio, &ranked);
}
-
-#[test]
-
-fn ordinary_input() {
-
- let mut arg: Vec<String> = Vec::new();
- let mut exp: Vec<String> = Vec::new();
- for s in vec!["", "", "Hej ☕ glade", "", "Amatör", "", ""] {
- arg.push(String::from(s));
- }
- for s in vec!["Hej ☕ glade", "Amatör"] {
- exp.push(String::from(s));
- }
- let (_, res) = winnow(arg);
- assert_eq!(exp, res);
-}
-
-#[test]
-
-fn whitespace() {
-
- let arg: Vec<String> =
- "\n\nHej du\t\n glade\nta en\n\n" // Convert &str to String
- .split("\n").map(|x| x.to_owned()) // with `.to_owned()` or
- .collect(); // `.to_string()`
- let exp: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- let (_, res) = winnow(arg);
- assert_eq!(exp, res);
-}
-
-#[test]
-
-fn bullets() {
-
- let arg: Vec<String> =
- "\n * Hej du\t\n - glade\n-\tta en\n\n"
- .split("\n").map(|x| x.to_owned())
- .collect();
- let exp: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- let (conf, res) = winnow(arg);
- assert_eq!(exp, res);
- assert_eq!(conf.bullet, " - ".to_owned());
- assert_eq!(conf.number, "".to_owned());
-}
-
-#[test]
-
-fn numbers() {
-
- let arg: Vec<String> =
- "1) Hej du\n 459. glade\n 2)ta en\n"
- .split("\n").map(|x| x.to_owned())
- .collect();
- let exp: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- let (conf, res) = winnow(arg);
- assert_eq!(exp, res);
- assert_eq!(conf.bullet, "".to_owned());
- assert_eq!(conf.number, " {}. ".to_owned());
-}
-
-#[test]
-
-fn prioinput() {
-
- let arg: Vec<String> =
- " - 70 % Hej du\n - 30 %\tglade\nta en\n"
- .split("\n").map(|x| x.to_owned())
- .collect();
- let exp: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- let (_, res) = winnow(arg);
- assert_eq!(exp, res);
-}
-
-#[test]
-
-fn pipe() {
-
- let arg: Vec<String> =
- "\n| Hej du\n | glade\n| ta en\n"
- .split("\n").map(|x| x.to_owned())
- .collect();
- let exp: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- let (conf, res) = winnow(arg);
- assert_eq!(exp, res);
- assert_eq!(conf.bullet, " | ".to_owned());
- assert_eq!(conf.number, "".to_owned());
-}
-
-#[test]
-
-fn bullout() {
-
- let conf = Conf {
- bullet: " * ".to_owned(),
- number: "".to_owned()};
- let prio = vec![0.6, 0.3, 0.1];
- let ranked: Vec<String> =
- "Hej du\nglade\nta en"
- .split("\n").map(|x| x.to_owned()).collect();
- output(&conf, &prio, &ranked);
-}