use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct LineTypes {
pub code: bool,
pub tests: bool,
pub examples: bool,
pub docs: bool,
pub comments: bool,
pub blanks: bool,
pub total: bool,
}
impl Default for LineTypes {
fn default() -> Self {
Self {
code: true,
tests: true,
examples: false,
docs: true,
comments: false,
blanks: false,
total: true,
}
}
}
impl LineTypes {
pub fn new() -> Self {
Self {
code: false,
tests: false,
examples: false,
docs: false,
comments: false,
blanks: false,
total: true, }
}
pub fn everything() -> Self {
Self {
code: true,
tests: true,
examples: true,
docs: true,
comments: true,
blanks: true,
total: true,
}
}
pub fn none() -> Self {
Self {
code: false,
tests: false,
examples: false,
docs: false,
comments: false,
blanks: false,
total: false,
}
}
pub fn code_only() -> Self {
Self::new().with_code()
}
pub fn tests_only() -> Self {
Self::new().with_tests()
}
pub fn examples_only() -> Self {
Self::new().with_examples()
}
pub fn logic_only() -> Self {
Self {
code: true,
tests: true,
examples: true,
docs: false,
comments: false,
blanks: false,
total: true,
}
}
pub fn with_code(mut self) -> Self {
self.code = true;
self
}
pub fn with_tests(mut self) -> Self {
self.tests = true;
self
}
pub fn with_examples(mut self) -> Self {
self.examples = true;
self
}
pub fn with_docs(mut self) -> Self {
self.docs = true;
self
}
pub fn with_comments(mut self) -> Self {
self.comments = true;
self
}
pub fn with_blanks(mut self) -> Self {
self.blanks = true;
self
}
pub fn with_total(mut self) -> Self {
self.total = true;
self
}
pub fn without_total(mut self) -> Self {
self.total = false;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum Aggregation {
#[default]
Total,
ByCrate,
ByModule,
ByFile,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum OrderBy {
#[default]
Label,
Code,
Tests,
Examples,
Docs,
Comments,
Blanks,
Total,
}
impl FromStr for OrderBy {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"label" | "name" | "path" => Ok(OrderBy::Label),
"code" => Ok(OrderBy::Code),
"tests" | "test" => Ok(OrderBy::Tests),
"examples" | "example" => Ok(OrderBy::Examples),
"docs" | "doc" => Ok(OrderBy::Docs),
"comments" | "comment" => Ok(OrderBy::Comments),
"blanks" | "blank" => Ok(OrderBy::Blanks),
"total" => Ok(OrderBy::Total),
_ => Err(format!("Unknown order field: {}", s)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Field {
Code,
Tests,
Examples,
Docs,
Comments,
Blanks,
Total,
}
impl Field {
pub fn name(&self) -> &'static str {
match self {
Field::Code => "code",
Field::Tests => "tests",
Field::Examples => "examples",
Field::Docs => "docs",
Field::Comments => "comments",
Field::Blanks => "blanks",
Field::Total => "total",
}
}
pub fn all() -> &'static [Field] {
&[
Field::Code,
Field::Tests,
Field::Examples,
Field::Docs,
Field::Comments,
Field::Blanks,
Field::Total,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Op {
Gt,
Gte,
Eq,
Ne,
Lt,
Lte,
}
impl Op {
pub fn name(&self) -> &'static str {
match self {
Op::Gt => "gt",
Op::Gte => "gte",
Op::Eq => "eq",
Op::Ne => "ne",
Op::Lt => "lt",
Op::Lte => "lte",
}
}
pub fn all() -> &'static [Op] {
&[Op::Gt, Op::Gte, Op::Eq, Op::Ne, Op::Lt, Op::Lte]
}
pub fn evaluate(&self, lhs: i64, rhs: i64) -> bool {
match self {
Op::Gt => lhs > rhs,
Op::Gte => lhs >= rhs,
Op::Eq => lhs == rhs,
Op::Ne => lhs != rhs,
Op::Lt => lhs < rhs,
Op::Lte => lhs <= rhs,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Predicate {
pub field: Field,
pub op: Op,
pub value: u64,
}
impl Predicate {
pub fn new(field: Field, op: Op, value: u64) -> Self {
Self { field, op, value }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum OrderDirection {
#[default]
Ascending,
Descending,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Ordering {
pub by: OrderBy,
pub direction: OrderDirection,
}
impl Default for Ordering {
fn default() -> Self {
Self {
by: OrderBy::Label,
direction: OrderDirection::Ascending,
}
}
}
impl Ordering {
pub fn by_label() -> Self {
Self::default()
}
pub fn by_code() -> Self {
Self {
by: OrderBy::Code,
direction: OrderDirection::Descending,
}
}
pub fn by_tests() -> Self {
Self {
by: OrderBy::Tests,
direction: OrderDirection::Descending,
}
}
pub fn by_total() -> Self {
Self {
by: OrderBy::Total,
direction: OrderDirection::Descending,
}
}
pub fn ascending(mut self) -> Self {
self.direction = OrderDirection::Ascending;
self
}
pub fn descending(mut self) -> Self {
self.direction = OrderDirection::Descending;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_types_default() {
let lt = LineTypes::default();
assert!(lt.code);
assert!(lt.tests);
assert!(!lt.examples); assert!(lt.docs);
assert!(!lt.comments); assert!(!lt.blanks); assert!(lt.total);
}
#[test]
fn test_line_types_none() {
let lt = LineTypes::none();
assert!(!lt.code);
assert!(!lt.tests);
assert!(!lt.examples);
assert!(!lt.docs);
assert!(!lt.comments);
assert!(!lt.blanks);
assert!(!lt.total);
}
#[test]
fn test_line_types_everything() {
let lt = LineTypes::everything();
assert!(lt.code);
assert!(lt.tests);
assert!(lt.examples);
assert!(lt.docs);
assert!(lt.comments);
assert!(lt.blanks);
assert!(lt.total);
}
#[test]
fn test_line_types_builder() {
let lt = LineTypes::new().with_code().with_tests();
assert!(lt.code);
assert!(lt.tests);
assert!(!lt.examples);
assert!(!lt.docs);
assert!(!lt.comments);
assert!(!lt.blanks);
assert!(lt.total); }
#[test]
fn test_line_types_code_only() {
let lt = LineTypes::code_only();
assert!(lt.code);
assert!(!lt.tests);
assert!(!lt.examples);
assert!(!lt.docs);
assert!(!lt.comments);
assert!(!lt.blanks);
assert!(lt.total); }
#[test]
fn test_line_types_logic_only() {
let lt = LineTypes::logic_only();
assert!(lt.code);
assert!(lt.tests);
assert!(lt.examples);
assert!(!lt.docs);
assert!(!lt.comments);
assert!(!lt.blanks);
assert!(lt.total);
}
#[test]
fn test_ordering_default() {
let ordering = Ordering::default();
assert_eq!(ordering.by, OrderBy::Label);
assert_eq!(ordering.direction, OrderDirection::Ascending);
}
#[test]
fn test_ordering_by_code() {
let ordering = Ordering::by_code();
assert_eq!(ordering.by, OrderBy::Code);
assert_eq!(ordering.direction, OrderDirection::Descending);
}
#[test]
fn test_ordering_direction_builder() {
let ordering = Ordering::by_total().ascending();
assert_eq!(ordering.by, OrderBy::Total);
assert_eq!(ordering.direction, OrderDirection::Ascending);
}
#[test]
fn test_op_evaluate_all_variants() {
assert!(Op::Gt.evaluate(10, 5));
assert!(!Op::Gt.evaluate(5, 5));
assert!(Op::Gte.evaluate(5, 5));
assert!(Op::Gte.evaluate(10, 5));
assert!(!Op::Gte.evaluate(4, 5));
assert!(Op::Eq.evaluate(5, 5));
assert!(!Op::Eq.evaluate(5, 6));
assert!(Op::Ne.evaluate(5, 6));
assert!(!Op::Ne.evaluate(5, 5));
assert!(Op::Lt.evaluate(4, 5));
assert!(!Op::Lt.evaluate(5, 5));
assert!(Op::Lte.evaluate(5, 5));
assert!(Op::Lte.evaluate(4, 5));
assert!(!Op::Lte.evaluate(6, 5));
}
#[test]
fn test_op_evaluate_handles_negative_diffs() {
assert!(Op::Lt.evaluate(-100, 0));
assert!(Op::Gte.evaluate(-50, -100));
}
#[test]
fn test_field_all_and_op_all_lengths() {
assert_eq!(Field::all().len(), 7);
assert_eq!(Op::all().len(), 6);
}
#[test]
fn test_predicate_construction() {
let p = Predicate::new(Field::Code, Op::Gte, 100);
assert_eq!(p.field, Field::Code);
assert_eq!(p.op, Op::Gte);
assert_eq!(p.value, 100);
}
#[test]
fn test_order_by_from_str() {
assert_eq!(OrderBy::from_str("code").unwrap(), OrderBy::Code);
assert_eq!(OrderBy::from_str("tests").unwrap(), OrderBy::Tests);
assert_eq!(OrderBy::from_str("total").unwrap(), OrderBy::Total);
assert_eq!(OrderBy::from_str("label").unwrap(), OrderBy::Label);
assert_eq!(OrderBy::from_str("docs").unwrap(), OrderBy::Docs);
assert_eq!(OrderBy::from_str("comments").unwrap(), OrderBy::Comments);
assert_eq!(OrderBy::from_str("blanks").unwrap(), OrderBy::Blanks);
assert!(OrderBy::from_str("invalid").is_err());
}
}