]> git.g-eek.se Git - ranknauto.git/commitdiff
Src. Refactor and migrate all logic into a library
authorGustav Eek <gustav.eek@fripost.org>
Thu, 25 May 2023 20:28:41 +0000 (22:28 +0200)
committerGustav Eek <gustav.eek@fripost.org>
Mon, 25 Dec 2023 19:46:14 +0000 (20:46 +0100)
Create *src/lib.rs* (and reduce *src/main.rs*) for all logic.

TODO Next is further separation into
files *main*, *run*, *parse* (or *io*),
and *dist* (or *distributions*).

src/lib.rs [new file with mode: 0644]
src/main.rs

diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..c971262
--- /dev/null
@@ -0,0 +1,329 @@
+//! 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);
+}
index 1f90fc4a948b2f2081e7b9971edf460fd668cbb1..19f552a0e3e95a625d822b993a768aad3ef52b53 100644 (file)
 //!
 //! 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() {
 
@@ -234,6 +29,7 @@ fn main() {
         else if a == "moderate" {1.0}
         else if a == "heavy" {2.0}
         else {1.0},
+
     };
 
     if DEBUG { eprintln!("Finally a skewness: {}", skew) }
@@ -258,114 +54,3 @@ fn main() {
 
     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);
-}