code:
//! A confusion matrix is used in data-mining as a summary of the performance
//! of a classification algorithm.
//!
//! This crate allows you to create an empty confusion matrix, and then
//! populate it incrementally with the (actual, predicted) results from a
//! data-mining experiment. At any point, various statistical measures
//! and results may be computed.
//!

use std::collections::HashMap;
use std::fmt;
use std::ops;

/// A confusion matrix is used to record pairs of (actual class, predicted class)
/// as typically produced by a classification algorithm.
///
/// It is designed to be called incrementally, as results are obtained
/// from the classifier model. Class labels currently must be strings.
///
/// At any point, statistics may be obtained by calling the relevant methods.
///
/// A two-class example is:
/// ```text
///     Predicted       Predicted     |
///     Positive        Negative      | Actual
///     ------------------------------+------------
///         a               b         | Positive
///         c               d         | Negative
/// ```
///
/// Here:
///
/// * a is the number of true positives (those labelled positive and classified
///   positive)
/// * b is the number of false negatives (those labelled positive but classified
///   negative)
/// * c is the number of false positives (those labelled negative but classified
///   positive)
/// * d is the number of true negatives (those labelled negative and classified
///   negative)
///
/// From this table, we can calculate statistics like:
///
/// * true_positive_rate = a/(a+b)
/// * positive recall =    a/(a+c)
///
/// The implementation supports confusion matrices with more than two classes,
/// and hence most statistics are calculated with reference to a named class.
/// When more than two classes are in use, the statistics are calculated as if
/// the named class were positive and all the other classes are grouped as if
/// negative.
///
/// For example, in a three-class example:
///
/// ```text
/// Predicted       Predicted     Predicted     |
///    Red            Blue          Green       | Actual
/// --------------------------------------------+------------
///     a               b             c         | Red
///     d               e             f         | Blue
///     g               h             i         | Green
/// ```
/// We can calculate:
///
/// * true_red_rate = a/(a+b+c)
/// * red recall =    a/(a+d+g)
///
/// # Example
///
/// The following example creates a simple two-class confusion matrix, prints
/// a few statistics and displays the table.
///
/// ```ignore
/// use confusion_matrix;
///
/// fn main() {
///     let mut cm = confusion_matrix::new();
///
///     cm[("pos", "pos")] = 10;
///     cm[("pos", "neg")] = 5;
///     cm[("neg", "neg")] = 20;
///     cm[("neg", "pos")] = 3;
///
///     println!("Precision: {}", cm.precision("pos"));
///     println!("Recall: {}", cm.recall("pos"));
///     println!("MCC: {}", cm.matthews_correlation("pos"));
///     println!("");
///     println!("{}", cm);
/// }
/// ```
/// Output:
///
/// ```text
/// Precision: 0.7692307692307693
/// Recall: 0.6666666666666666
/// MCC: 0.5524850114241865
///
/// Predicted |
/// neg pos   | Actual
/// ----------+-------
///  20   3   | neg
///   5  10   | pos
/// ```
///
#[derive(Clone,Debug,Default,Eq,PartialEq)]
pub struct ConfusionMatrix {
    matrix: HashMap<String, HashMap<String, usize>>,
}

/// Creates a new empty instance of a confusion matrix.
///
/// # Example
/// ```
/// let mut cm = confusion_matrix::new();
/// ```
///
pub fn new() -> ConfusionMatrix {
    ConfusionMatrix {
        matrix: HashMap::new(),
    }
}

impl fmt::Display for ConfusionMatrix {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let ls = self.labels();
        let mut result = String::new();

        let mut title_line = String::from("Predicted ");
        let mut label_line = String::new();

        for l in &ls {
            label_line.push_str(&l);
            label_line.push(' ');
        }
        while label_line.len() < title_line.len() {
            label_line.push(' ');
        }
        while title_line.len() < label_line.len() {
            title_line.push(' ');
        }
        result.push_str(&title_line);
        result.push_str("|\n");
        result.push_str(&label_line);
        result.push_str("| Actual\n");

        for _ in 0..title_line.len() {
            result.push('-');
        }
        result.push_str("+-------\n");

        for l in &ls {
            let mut count_line = String::new();

            for i in 0..ls.len() {
                count_line.push_str(&format!("{:>1$} ", self.count_for(&l, &ls[i]), ls[i].len()));
            }

            result.push_str(&format!("{:<2$}| {}\n", &count_line, &l, title_line.len()));
        }

        write!(f, "{}", result)
    }
}

impl ops::Index<(&str, &str)> for ConfusionMatrix {
    type Output = usize;

    /// Returns the count for an (actual, prediction) pair, or 0 if the pair
    /// is not known.
    ///
    /// * `actual` - the actual class of the instance, which we are hoping
    ///              the classifier will predict.
    /// * `prediction` - the predicted class for the instance, as output from
    ///                  the classifier.
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(2, cm[("positive", "positive")]);
    /// assert_eq!(0, cm[("positive", "not_known")]);
    /// ```
    ///
    fn index(&self, (actual, prediction): (&str, &str)) -> &usize {
        if let Some(predictions) = self.matrix.get(actual) {
            if let Some(count) = predictions.get(prediction) {
                return count;
            }
        }
        &0
    }
}

impl ops::IndexMut<(&str, &str)> for ConfusionMatrix {
    /// Provides a mutable reference to the count for an (actual, prediction) pair.
    ///
    /// * `actual` - the actual class of the instance, which we are hoping
    ///              the classifier will predict.
    /// * `prediction` - the predicted class for the instance, as output from
    ///                  the classifier.
    ///
    /// # Example
    ///
    /// ```
    /// let mut cm = confusion_matrix::new();
    /// for _ in 0..2 { cm[("positive", "positive")] += 1; }
    /// cm[("positive", "negative")] = 5;
    /// cm[("negative", "positive")] = 1;
    /// for _ in 0..3 { cm[("negative", "negative")] += 1; }
    /// assert_eq!(2, cm[("positive", "positive")]);
    /// assert_eq!(5, cm[("positive", "negative")]);
    /// assert_eq!(0, cm[("positive", "not_known")]);
    /// ```
    ///
    fn index_mut(&mut self, (actual, prediction): (&str, &str)) -> &mut usize {
        // make sure there is a slot for (actual, prediction)
        if !self.matrix.contains_key(actual) {
            self.matrix.insert(String::from(actual), HashMap::new());
        }
        if let Some(predictions) = self.matrix.get_mut(actual) {
            if None == predictions.get(prediction) {
                predictions.insert(String::from(prediction), 0);
            }
        }
        // return a mutable reference to (actual, prediction) slot
        self.matrix.get_mut(actual)
            .expect("Confusion matrix must contain actual value")
            .get_mut(prediction)
            .expect("Confusion matrix must contain predicted value")
    }
}

impl ConfusionMatrix {
    /// Adds one result to the matrix.
    ///
    /// * `actual` - the actual class of the instance, which we are hoping
    ///              the classifier will predict.
    /// * `prediction` - the predicted class for the instance, as output from
    ///                  the classifier.
    ///
    /// # Example
    ///
    /// The following table can be made as:
    /// ```text
    ///     Predicted       Predicted     |
    ///     Positive        Negative      | Actual
    ///     ------------------------------+------------
    ///         2               5         | Positive
    ///         1               3         | Negative
    /// ```
    ///
    /// ```
    /// let mut cm = confusion_matrix::new();
    /// for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// ```
    ///
    pub fn add_for(&mut self, actual: &str, prediction: &str) {
        if !self.matrix.contains_key(actual) {
            self.matrix.insert(String::from(actual), HashMap::new());
        }
        if let Some(predictions) = self.matrix.get_mut(actual) {
            let mut next_count = 1;
            if let Some(count) = predictions.get(prediction) {
                next_count = *count + 1;
            }
            predictions.insert(String::from(prediction), next_count);
        }
    }

    /// Returns the count for an (actual, prediction) pair, or 0 if the pair
    /// is not known.
    ///
    /// * `actual` - the actual class of the instance, which we are hoping
    ///              the classifier will predict.
    /// * `prediction` - the predicted class for the instance, as output from
    ///                  the classifier.
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(2, cm.count_for("positive", "positive"));
    /// assert_eq!(0, cm.count_for("positive", "not_known"));
    /// ```
    ///
    pub fn count_for(&self, actual: &str, prediction: &str) -> usize {
        if let Some(predictions) = self.matrix.get(actual) {
            if let Some(count) = predictions.get(prediction) {
                return *count;
            }
        }
        0
    }

    /// Returns the number of instances of the given class label which
    /// are incorrectly classified.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(5, cm.false_negative("positive"));
    /// assert_eq!(1, cm.false_negative("negative"));
    /// ```
    ///
    pub fn false_negative(&self, label: &str) -> usize {
        let mut total = 0;

        if let Some(predictions) = self.matrix.get(label) {
            for (key, count) in predictions.iter() {
                if key != label {
                    total += *count;
                }
            }
        }

        total
    }

    /// Returns the number of incorrectly classified instances with the given
    /// class label.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(1, cm.false_positive("positive"));
    /// assert_eq!(5, cm.false_positive("negative"));
    /// ```
    ///
    pub fn false_positive(&self, label: &str) -> usize {
        let mut total = 0;

        for (key, predictions) in self.matrix.iter() {
            if key != label {
                if let Some(count) = predictions.get(label) {
                    total += *count;
                }
            }
        }

        total
    }

    /// Returns the proportion of instances of the given class label which
    /// are incorrectly classified out of all those instances not originally
    /// of that label.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert!((cm.false_rate("positive") - 1.0/4.0).abs() < 0.001); // true_rate("positive") = 1/(1+3)
    /// assert!((cm.false_rate("negative") - 5.0/7.0).abs() < 0.001); // true_rate("negative") = 5/(5+2)
    /// ```
    ///
    pub fn false_rate(&self, label: &str) -> f64 {
        let fp = self.false_positive(label);
        let tn = self.true_negative(label);

        divide(fp, fp + tn)
    }

    /// Returns the F-measure for a given class label, which is the harmonic
    /// mean of the precision and recall for that label.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn f_measure(&self, label: &str) -> f64 {
        let divisor = self.precision(label) + self.recall(label);
        if divisor == 0.0 {
            0.0
        } else {
            2.0 * self.precision(label) * self.recall(label) / divisor
        }
    }

    /// Returns the geometric mean of the true rates for each class label.
    ///
    pub fn geometric_mean(&self) -> f64 {
        if self.matrix.is_empty() {
            0.0
        } else {
            let mut product = 1.0;

            for key in self.matrix.keys() {
                product *= self.true_rate(key);
            }

            product.powf(1.0 / (self.matrix.len() as f64))
        }
    }

    /// Returns Cohen's Kappa Statistic, which is a measure of the quality
    /// of binary classification.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn kappa(&self, label: &str) -> f64 {
        let tp = self.true_positive(label);
        let ff = self.false_negative(label);
        let fp = self.false_positive(label);
        let tn = self.true_negative(label);
        let total = tp + ff + fp + tn;
        let total_accuracy = divide(tp + tn, tp + tn + fp + ff);
        let random_accuracy = divide((tn + fp) * (tn + ff) + (ff + tp) * (fp + tp), total * total);

        if random_accuracy == 1.0 {
            0.0
        } else {
            (total_accuracy - random_accuracy) / (1.0 - random_accuracy)
        }
    }

    /// Returns a sorted vector of the class labels contained in the matrix.
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(vec!["negative", "positive"], cm.labels());
    /// ```
    ///
    pub fn labels(&self) -> Vec<String> {
        let mut result = vec![];

        for (key, predictions) in self.matrix.iter() {
            result.push(key.clone());
            for key in predictions.keys() {
                result.push(key.clone());
            }
        }

        result.sort();
        result.dedup();
        result
    }

    /// Returns the Matthews Correlation Coefficient, which is a measure of
    /// the quality of binary classification.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn matthews_correlation(&self, label: &str) -> f64 {
        let tp = self.true_positive(label);
        let ff = self.false_negative(label);
        let fp = self.false_positive(label);
        let tn = self.true_negative(label);
        let divisor = (((tp + fp) * (tp + ff) * (tn + fp) * (tn + ff)) as f64).sqrt();

        if divisor == 0.0 {
            0.0
        } else {
            ((tp * tn - fp * ff) as f64) / divisor
        }
    }

    /// Returns the proportion of instances which are correctly labelled.
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert!((cm.overall_accuracy() - (2.0+3.0)/11.0) < 0.001); // overall_accuracy() = (2+3)/(2+5+1+3)
    /// ```
    ///
    pub fn overall_accuracy(&self) -> f64 {
        let mut total_correct = 0;

        for key in self.matrix.keys() {
            total_correct += self.true_positive(key);
        }

        divide(total_correct, self.total())
    }

    /// Returns the proportion of instances classified as the given class label
    /// which are correct.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert!((cm.precision("positive") - 2.0/3.0) < 0.001); // precision("positive") = 2/(2+1)
    /// ```
    ///
    pub fn precision(&self, label: &str) -> f64 {
        let tp = self.true_positive(label);
        let fp = self.false_positive(label);

        divide(tp, tp + fp)
    }

    /// Returns the proportion of instances of the given label,
    /// out of the total.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert!((cm.prevalence("positive") - 7.0/11.0).abs() < 0.001); // prevalence = (2+5)/(2+5+1+3)
    /// ```
    ///
    pub fn prevalence(&self, label: &str) -> f64 {
        let tp = self.true_positive(label);
        let ff = self.false_negative(label);
        let fp = self.false_positive(label);
        let tn = self.true_negative(label);
        let total = tp + ff + fp + tn;

        divide(tp + ff, total)
    }

    /// Recall is another name for the true positive rate for that label.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn recall(&self, label: &str) -> f64 {
        self.true_rate(label)
    }

    /// Sensitivity is another name for the true positive rate (recall) for
    /// that label.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn sensitivity(&self, label: &str) -> f64 {
        self.true_rate(label)
    }

    /// Returns 1 - false_rate(label)
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    pub fn specificity(&self, label: &str) -> f64 {
        1.0 - self.false_rate(label)
    }

    /// Returns the total number of instances referenced in the matrix.
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(11, cm.total());
    /// ```
    ///
    pub fn total(&self) -> usize {
        let mut total = 0;

        for predictions in self.matrix.values() {
            for count in predictions.values() {
                total += *count;
            }
        }

        total
    }

    /// Returns the number of instances NOT of the given class label which
    /// are correctly classified.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(3, cm.true_negative("positive"));
    /// assert_eq!(2, cm.true_negative("negative"));
    /// ```
    ///
    pub fn true_negative(&self, label: &str) -> usize {
        let mut total = 0;

        for (key, predictions) in self.matrix.iter() {
            if key != label {
                if let Some(count) = predictions.get(key) {
                    total += *count;
                }
            }
        }

        total
    }

    /// Returns the number of instances of the given class label which
    /// are correctly classified.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert_eq!(2, cm.true_positive("positive"));
    /// assert_eq!(3, cm.true_positive("negative"));
    /// ```
    ///
    pub fn true_positive(&self, label: &str) -> usize {
        if let Some(predictions) = self.matrix.get(label) {
            if let Some(count) = predictions.get(label) {
                return *count;
            }
        }
        0
    }

    /// Returns the proportion of instances of the given class label which
    /// are correctly classified.
    ///
    /// * `label` - the class label to treat as "positive".
    ///
    /// # Example
    /// (using `cm` from [`Self::add_for()`])
    ///
    /// ```
    /// # let mut cm = confusion_matrix::new();
    /// # for _ in 0..2 { cm.add_for("positive", "positive"); }
    /// # for _ in 0..5 { cm.add_for("positive", "negative"); }
    /// # for _ in 0..1 { cm.add_for("negative", "positive"); }
    /// # for _ in 0..3 { cm.add_for("negative", "negative"); }
    /// assert!((cm.true_rate("positive") - 2.0/7.0) < 0.001); // true_rate("positive") = 2/(2+5)
    /// assert!((cm.true_rate("negative") - 3.0/4.0) < 0.001); // true_rate("negative") = 3/(1+3)
    /// ```
    ///
    pub fn true_rate(&self, label: &str) -> f64 {
        let tp = self.true_positive(label);
        let ff = self.false_negative(label);

        divide(tp, tp + ff)
    }
}

fn divide(x: usize, y: usize) -> f64 {
    if y == 0 {
        0.0
    } else {
        (x as f64) / (y as f64)
    }
}

#[cfg(test)]
mod tests {
    use super::new;

    fn test_approx_same(num1: f64, num2: f64) {
        assert!((num1 - num2).abs() < 0.001);
    }

    #[test]
    fn test_empty_case() {
        let cm = new();
        assert_eq!(0, cm.total());
        assert_eq!(0, cm.true_positive("none"));
        assert_eq!(0, cm.false_negative("none"));
        assert_eq!(0, cm.false_positive("none"));
        assert_eq!(0, cm.true_negative("none"));
        test_approx_same(0.0, cm.true_rate("none"));
    }

    #[test]
    fn test_two_classes() {
        let mut cm = new();
        for _ in 0..10 {
            cm.add_for("pos", "pos");
        }
        for _ in 0..5 {
            cm[("pos", "neg")] += 1;
        }
        cm[("neg", "neg")] = 20;
        cm[("neg", "pos")] = 5;

        assert_eq!(vec!["neg", "pos"], cm.labels());
        assert_eq!(10, cm[("pos", "pos")]);
        assert_eq!(5, cm[("pos", "neg")]);
        assert_eq!(20, cm[("neg", "neg")]);
        assert_eq!(5, cm[("neg", "pos")]);

        assert_eq!(40, cm.total());
        assert_eq!(10, cm.true_positive("pos"));
        assert_eq!(5, cm.false_negative("pos"));
        assert_eq!(5, cm.false_positive("pos"));
        assert_eq!(20, cm.true_negative("pos"));
        assert_eq!(20, cm.true_positive("neg"));
        assert_eq!(5, cm.false_negative("neg"));
        assert_eq!(5, cm.false_positive("neg"));
        assert_eq!(10, cm.true_negative("neg"));

        test_approx_same(0.6667, cm.true_rate("pos"));
        test_approx_same(0.8, cm.true_rate("neg"));
        test_approx_same(0.2, cm.false_rate("pos"));
        test_approx_same(0.3333, cm.false_rate("neg"));
        test_approx_same(0.6667, cm.precision("pos"));
        test_approx_same(0.8, cm.precision("neg"));
        test_approx_same(0.6667, cm.recall("pos"));
        test_approx_same(0.8, cm.recall("neg"));
        test_approx_same(0.6667, cm.sensitivity("pos"));
        test_approx_same(0.8, cm.sensitivity("neg"));
        test_approx_same(0.75, cm.overall_accuracy());
        test_approx_same(0.6667, cm.f_measure("pos"));
        test_approx_same(0.8, cm.f_measure("neg"));
        test_approx_same(0.7303, cm.geometric_mean());
    }

    // Example from:
    // https://www.datatechnotes.com/2019/02/accuracy-metrics-in-classification.html
    #[test]
    fn test_two_classes_2() {
        let mut cm = new();
        cm[("pos", "pos")] = 5;
        cm[("pos", "neg")] = 1;
        cm[("neg", "neg")] = 3;
        cm[("neg", "pos")] = 2;

        assert_eq!(11, cm.total());
        assert_eq!(5, cm.true_positive("pos"));
        assert_eq!(1, cm.false_negative("pos"));
        assert_eq!(2, cm.false_positive("pos"));
        assert_eq!(3, cm.true_negative("pos"));

        test_approx_same(0.7142, cm.precision("pos"));
        test_approx_same(0.8333, cm.recall("pos"));
        test_approx_same(0.7272, cm.overall_accuracy());
        test_approx_same(0.7692, cm.f_measure("pos"));
        test_approx_same(0.8333, cm.sensitivity("pos"));
        test_approx_same(0.6, cm.specificity("pos"));
        test_approx_same(0.4407, cm.kappa("pos"));
        test_approx_same(0.5454, cm.prevalence("pos"));
    }

    // Examples from:
    // https://standardwisdom.com/softwarejournal/2011/12/matthews-correlation-coefficient-how-well-does-it-do/
    fn two_class_case(
        a: usize,
        b: usize,
        c: usize,
        d: usize,
        e: f64,
        f: f64,
        g: f64,
        h: f64,
        i: f64,
        ) {
        let mut cm = new();
        cm[("pos", "pos")] = a;
        cm[("pos", "neg")] = b;
        cm[("neg", "neg")] = c;
        cm[("neg", "pos")] = d;

        test_approx_same(e, cm.matthews_correlation("pos"));
        test_approx_same(f, cm.precision("pos"));
        test_approx_same(g, cm.recall("pos"));
        test_approx_same(h, cm.f_measure("pos"));
        test_approx_same(i, cm.kappa("pos"));
    }

    #[test]
    fn test_two_classes_3() {
        two_class_case(100, 0, 900, 0, 1.0, 1.0, 1.0, 1.0, 1.0);
        two_class_case(65, 35, 825, 75, 0.490, 0.4643, 0.65, 0.542, 0.4811);
        two_class_case(50, 50, 700, 200, 0.192, 0.2, 0.5, 0.286, 0.1666);
    }

    #[test]
    fn test_three_classes() {
        let mut cm = new();
        for _ in 0..10 {
            cm.add_for("red", "red");
        }
        for _ in 0..7 {
            cm.add_for("red", "blue");
        }
        for _ in 0..5 {
            cm.add_for("red", "green");
        }
        for _ in 0..20 {
            cm.add_for("blue", "red");
        }
        for _ in 0..5 {
            cm.add_for("blue", "blue");
        }
        for _ in 0..15 {
            cm.add_for("blue", "green");
        }
        for _ in 0..30 {
            cm.add_for("green", "red");
        }
        for _ in 0..12 {
            cm.add_for("green", "blue");
        }
        for _ in 0..8 {
            cm.add_for("green", "green");
        }

        assert_eq!(vec!["blue", "green", "red"], cm.labels());
        assert_eq!(112, cm.total());
        assert_eq!(10, cm.true_positive("red"));
        assert_eq!(12, cm.false_negative("red"));
        assert_eq!(50, cm.false_positive("red"));
        assert_eq!(13, cm.true_negative("red"));
        assert_eq!(5, cm.true_positive("blue"));
        assert_eq!(35, cm.false_negative("blue"));
        assert_eq!(19, cm.false_positive("blue"));
        assert_eq!(18, cm.true_negative("blue"));
        assert_eq!(8, cm.true_positive("green"));
        assert_eq!(42, cm.false_negative("green"));
        assert_eq!(20, cm.false_positive("green"));
        assert_eq!(15, cm.true_negative("green"));
    }

    #[test]
    fn check_traits_clone_eq() {
        let mut cm = new();
        cm[("pos", "pos")] = 3;
        cm[("pos", "neg")] = 2;
        cm[("neg", "neg")] = 4;
        cm[("neg", "pos")] = 5;
        let mut cm_clone = cm.clone();

        assert_eq!(cm.total(), cm_clone.total());
        assert_eq!(14, cm.total());
        assert_eq!(14, cm_clone.total());
        assert_eq!(cm, cm_clone);

        cm_clone.add_for("pos", "pos");
        assert_eq!(14, cm.total());
        assert_eq!(15, cm_clone.total());
        assert!(cm != cm_clone);
    }

    #[test]
    fn check_default() {
        let cm = super::ConfusionMatrix::default();
        assert_eq!(0, cm.total());
    }
}


code:
avatar/
files/
file/
local/
logs/
log/
run/
config.php
.htaccess
httpd.conf
dataobject.ini
*.bak
*.orig
*.rej
TODO.rym
config-*.php
good-config.php
*.mo
/vendor/

repo_name: suraj/sloth

path: .gitignore

license: mit


code:
# GNU social - Log of Changes

## 2.0.0 - THIS. IS. GNU SOCIAL!!! [WIP]

Release name chosen after 300 by Frank Miller where the main protagonist Leonidas, King of Sparta, declines peace with the
Persians, after being disrespected, by shouting at the Persian Messenger "This is Sparta!" and kicking him into a large well
proceeded by the killing of the other Persian messengers.

### Major changes from previous release:

Load and Storage:
- New media handling system
- GS is now structurely divided in includes and public
- OEmbed upgraded to Embed plugin (Now we provide Open Graph information too)
- Added Redis based caching and queues
- Improved memcached support

Federation:
- Added ActivityPub support
- RemoteFollow: Remote follow ActivityPub and OStatus remote actors via the click of a button
- ActorLists: Allow to create collections of Actors and to interact with them - supports both OStatus and ActivityPub
- The Free Network: Automagically migrate internal remote profiles between Free Network protocols
- Enabled the search box to import remote notices and profiles
- Improvements on Nodeinfo (and new route)

General:
- Composer was integrated

Modules:
- Restored built-in plugins
- New modules system: core plugins and plugins physically separated
- Bug fixes of core modules logic
- Improved plugin management tool (now all available plugins are shown and third party can be uploaded and deleted)

## v1.20.9release - The Invicta Crusade

Dropped Support for PHP5.6.x. Minimum PHP version now is 7.0.0.

Release name chosen after Porto city. Porto is one of the oldest cities in Europe and thanks to its fierce resistance during
two battles and sieges in history, it has earned the epithet of ‘Cidade Invicta’ (Invincible City). The dev team behind this
release studies in Porto, Portugal.

Major changes from previous release:

- Various patches on PEAR related components
- Various database related improvements
- Improved XMPP support
- Added Nodeinfo support
- Various i18n and l10n bug fixes
- Improvements on Internal Session Handler
- Improvements on OpenID support
- Improved Media handling and safer upload
- Redirect to previous page after login
- Initial work on full conversion to PHP7
- Initial work on a better documentation
- Allow login with email
- Various bug fixes

## v1.2.0beta4 - The good reign of PHP5

Dropped support for PHP5.4.

New this version

This is the development branch for the 1.2.x version of GNU social. All daring 1.1.x admins should upgrade to this version.

So far it includes the following changes:

- Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support)

The last release, 1.1.3, gave us these improvements:

- XSS security fix (thanks Simon Waters, https://www.surevine.com/)
- Many improvements to ease adoption of the Qvitter front-end https://github.com/hannesmannerheim/qvitter
- Protocol adaptions for improved performance and stability

Upgrades from StatusNet 1.1.1 will also experience these improvements:

- Fixes for SQL injection errors in profile lists.
- Improved ActivityStreams JSON representation of activities and objects.
- Upgrade to the Twitter 1.1 API.
- More robust handling of errors in distribution.
- Fix error in OStatus subscription for remote groups.
- Fix error in XMPP distribution.
- Tracking of conversation URI metadata (more coherent convos)

## v1.1.3release - The Spanish Invasion

New this version

This is a security fix and bug fix release since 1.1.3-beta2. All 1.1.x sites should upgrade to this version.

So far it includes the following changes:

- XSS security fix (thanks Simon Waters, https://www.surevine.com/)
- Many improvements to ease adoption of the Qvitter front-end https://github.com/hannesmannerheim/qvitter
- Protocol adaptions for improved performance and stability
- Backing up a user's account now appears to work as it should

Upgrades from StatusNet 1.1.1 will also experience these improvements:

- Fixes for SQL injection errors in profile lists.
- Improved ActivityStreams JSON representation of activities and objects.
- Upgrade to the Twitter 1.1 API.
- More robust handling of errors in distribution.
- Fix error in OStatus subscription for remote groups.
- Fix error in XMPP distribution.
- Tracking of conversation URI metadata (more coherent convos)


code:
## Code of Conduct

### Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

### Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

### Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

### Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

### Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at mattl@gnu.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

### Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

## The Code of Conflict

GNU social has a high submission standard and we want to keep quality code in the
codebase and bad code out of it. As such your code will be closely scrutinized,
and you might take this criticism personally. Please understand that this is
meant to keep the standards of the codebase up, and isn't meant personally. All
the same, this isn't an excuse for poor behaviour, and a reviewer shouldn't be
misbehaving towards submitters.

If however, anyone feels personally abused, threatened, or otherwise
uncomfortable due to this process, that is not acceptable.  If so, please
contact the project team at mattl@gnu.org, and they will work to resolve the issue
to the best of their ability.

As a reviewer of code, please strive to keep things civil and focused on the
technical issues involved.  We are all humans, and frustrations can be high on
both sides of the process.  Try to keep in mind the immortal words of Bill and
Ted, "Be excellent to each other."


code: GNU social's contributing resources and instructions are made available at [DOCUMENTATION/DEVELOPERS](https://notabug.org/diogo/gnu-social/src/nightly/DOCUMENTATION/DEVELOPERS).


code:
Credits for GNU social
======================
The following is an incomplete list of developers
who've worked on GNU social, or its predecessors
StatusNet and Free Social. Apologies for any
oversight; please let mattl@gnu.org know if
anyone's been overlooked in error.

Current team
------------
* Matt Lee
* Mikael Nordfeldth
* Diogo Cordeiro
* Bruno Casteleiro
* Miguel Dantas
* Alexei Sorokin

Additional Contributors
-----------------------
* Ciaran Gultnieks
* Michael Landers
* Ori Avtalion
* Garret Buell
* Mike Cochrane
* Matthew Gregg
* Sean Murphy
* Leslie Michael Orchard
* Eric Helgeson
* Ken Sedgwick
* Brian Hendrickson
* Tobias Diekershoff
* Dan Moore
* Fil
* Jeff Mitchell
* Brenda Wallace
* Jeffery To
* Federico Marani
* mEDI
* Brett Taylor
* Brigitte Schuster
* Craig Andrews
* Donald Robertson
* Deb Nicholson
* Ian Denhart
* Steven DuBois
* Blaine Cook
* Henry Story
* Melvin Carvalho
* Stéphane Bérubé
* Akio
* Maiyannah Bishop
* Bob Mottram
* David Yip
* Neil E Hodges
* Moonman
* Normandy
* Verius
* Daniel Supernault

Credits for StatusNet
--------------
Leads
* Evan Prodromou
* Zach Copley

Team
* Earle Martin
* Marie-Claude Doyon
* Sarven Capadisli
* Robin Millette
* Brion Vibber
* James Walker
* Samantha Doherty
* Florian Biree
* Erik Stambaugh
* 'drry'
* Gina Haeussge
* Tryggvi Björgvinsson
* Adrian Lang
* Ori Avtalion
* Meitar Moscovitz
* Ken Sheppardson
* Simon Waters, Surevine
* Joshua Judson Rosen (rozzin)

Translators
-----------
* Siebrand Mazeland
* Tiago 'gouki' Faria
* TranslateWiki.net

A special thanks to the thousands of people who
have tried out GNU social, told their friends, and
built the fediverse network to what it is today.

License help from
-----------------
* Bradley M. Kuhn


code:
<?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Description of this file.
 *
 * @package   samples
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */

namespace samples;

defined('GNUSOCIAL') || die();

require_once __DIR__ . DIRECTORY_SEPARATOR . 'SampleHandler.php';

/**
 * Description of this class.
 *
 * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
 * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
 */
class MySampleClass
{
    /**
     * Constructor for the sample class.
     *
     * @param string $dummy_word just because.
     * @param int $result another just because.
     */
    public function __construct(string $dummy_word = '', ?int $result = null)
    {
        global $demo;
        $this->niceWorld();
    }

    /**
     * How cool is this function.
     *
     * @return string
     */
    public function niceWorld(): string
    {
        return 'hello, world.';
    }
}


code:
GNU social Coding Style
===========================

Please comply with [PSR-2](https://www.php-fig.org/psr/psr-2/) and the following standard when working on GNU social
if you want your patches accepted and modules included in supported releases.

If you see code which doesn't comply with the below, please fix it :)

Strings
-------------------------------------------------------------------------------
Use `'` instead of `"` for strings, where substitutions aren't required.
This is a performance issue, and prevents a lot of inconsistent coding styles.
When using substitutions, use curly braces around your variables - like so:

    $var = "my_var: {$my_var}";

Comments and Documentation
-------------------------------------------------------------------------------
Comments go on the line ABOVE the code, NOT to the right of the code, unless it is very short.
All functions and methods are to be documented using PhpDocumentor - https://docs.phpdoc.org/guides/

File Headers
-------------------------------------------------------------------------------
File headers follow a consistent format, as such:

    // This file is part of GNU social - https://www.gnu.org/software/social
    //
    // GNU social is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Affero General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // GNU social is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Affero General Public License for more details.
    //
    // You should have received a copy of the GNU Affero General Public License
    // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.

    /**
     * Description of this file.
     *
     * @package   samples
     * @author    Diogo Cordeiro <diogo@fc.up.pt>
     * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org
     * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
     */

Please use it.

A few notes:

*  The description of the file doesn't have to be exhaustive.  Rather it's
   meant to be a short summary of what's in this file and what it does.  Try
   to keep it to 1-5 lines.  You can get more in-depth when documenting
   individual functions!

*  You'll probably see files with multiple authors, this is by
   design - many people contributed to GNU social or its forebears!  If you
   are modifying an existing file, APPEND your own author line, and update
   the copyright year if needed. Do not replace existing ones.

You may find `boilerplate.php` useful when creating a new file from scratch.

Paragraph spacing
-------------------------------------------------------------------------------
Where-ever possible, try to keep the lines to 80 characters.  Don't
sacrifice readability for it though - if it makes more sense to have it in
one longer line, and it's more easily read that way, that's fine.

With assignments, avoid breaking them down into multiple lines unless
neccesary, except for enumerations and arrays.

'If' statements format
-------------------------------------------------------------------------------
Use switch statements where many else if's are going to be used. Switch/case is faster

    if ($var == 'example') {
        echo 'This is only an example';
    } else {
        echo 'This is not a test.  This is the real thing';
    }

Do NOT make if statements like this:

    if ($var == 'example'){ echo 'An example'; }

    OR this

    if($var = 'example')
            echo "An {$var}";

Associative arrays
-------------------------------------------------------------------------------
Always use `[]` instead of `array()`. Associative arrays must be written in the
following manner:

    $array = [
        'var' => 'value',
        'var2' => 'value2'
    ];

Note that spaces are preferred around the '=>'.

A note about shorthands
-------------------------------------------------------------------------------
Some short hands are evil:

- Use the long format for `<?php`.  Do NOT use `<?`.
- Use the long format for `<?php` echo. Do NOT use `<?=`.

Naming conventions
-------------------------------------------------------------------------------
Respect PSR2 first.

- Classes use PascalCase (e.g. MyClass).
- Functions/Methods use camelCase (e.g. myFunction).
- Variables use snake_case (e.g. my_variable).

A note on variable names, etc. It must be possible to understand what is meant
without neccesarialy seeing it in context, because the code that calls something
might not always make it clear.

So if you have something like:

    $notice->post($contents);

Well I can easily tell what you're doing there because the names are straight-
forward and clear.

Something like this:

    foo->bar();

Is much less clear.

Also, whereever possible, avoid ambiguous terms.  For example, don't use text
as a term for a variable.  Call back to "contents" above.

Comparisons
-------------------------------------------------------------------------------
Always use symbol based comparison operators (&&, ||) instead of text based
operators (AND, OR) as they are evaluated in different orders and at different
speeds.  This is will prevent any confusion or strange results.

Use English
-------------------------------------------------------------------------------
All variables, classes, methods, functions and comments must be in English.
Bad english is easier to work with than having to babelfish code to work out
how it works.

Encoding
-------------------------------------------------------------------------------
Files should be in UTF-8 encoding with UNIX line endings.

No ending tag
-------------------------------------------------------------------------------
Files should not end with an ending php tag "?>".  Any whitespace after the
closing tag is sent to the browser and cause errors, so don't include them.

Nesting Functions
-------------------------------------------------------------------------------
Avoid, if at all possible.  When not possible, document the living daylights
out of why you're nesting it.  It's not always avoidable, but PHP 5 has a lot
of obscure problems that come up with using nested functions.

If you must use a nested function, be sure to have robust error-handling.
This is a must and submissions including nested functions that do not have
robust error handling will be rejected and you'll be asked to add it.

Scoping
-------------------------------------------------------------------------------
Properly enforcing scope of functions is something many PHP programmers don't
do, but should.

In general:
*  Variables unique to a class should be protected and use interfacing to
   change them.  This allows for input validation and making sure we don't have
   injection, especially when something's exposed to the API, that any program
   can use, and not all of them are going to be be safe and trusted.

*  Variables not unique to a class should be validated prior to every call,
   which is why it's generally not a good idea to re-use stuff across classes
   unless there's significant performance gains to doing so.

*  Classes should protect functions that they do not want overriden, but they
   should avoid protecting the constructor and destructor and related helper
   functions as this prevents proper inheritance.

Typecasting
-------------------------------------------------------------------------------
PHP is a soft-typed language and it falls to us developers to make sure that
we are using the proper inputs.  Where ever possible use explicit type casting.
Where it in't, you're going to have to make sure that you check all your
inputs before you pass them.

All outputs should be cast as an explicit PHP type.

Not properly typecasting is a shooting offence.  Soft types let programmers
get away with a lot of lazy code, but lazy code is buggy code, and frankly, I
don't want it in GNU social if it's going to be buggy.

Consistent exception handling
-------------------------------------------------------------------------------
Consistency is key to good code to begin with, but it is especially important
to be consistent with how we handle errors.  GNU social has a variety of built-
in exception classes.  Use them, wherever it's possible and appropriate, and
they will do the heavy lifting for you.

Additionally, ensure you clean up any and all records and variables that need
cleanup in a function using try { } finally { } even if you do not plan on
catching exceptions (why wouldn't you, though?  That's silly.)

If you do not call an exception handler, you must, at a minimum, record errors
to the log using common_log(level, message)

Ensure all possible control flows of a function have exception handling and
cleanup, where appropriate.  Don't leave endpoints with unhandled exceptions.
Try not to leave something in an error state if it's avoidable.

Return values
-------------------------------------------------------------------------------
All functions must return a value.  Every single one.  This is not optional.

If you are simply making a procedure call, for example as part of a helper
function, then return boolean TRUE on success, and the exception on failure.

When returning the exception, return the whole nine yards, which is to say the
actual PHP exception object, not just an error message.

All return values not the above should be type cast, and you should sanitize
anything returned to ensure it fits into the cast.  You might technically make
an integer a string, for instance, but you should be making sure that integer
SHOULD be a string, if you're returning it, and that it is a valid return
value.

A vast majority of programming errors come down to not checking your inputs
and outputs properly, so please try to do so as best and thoroughly as you can.

Layout and Location of files
-------------------------------------------------------------------------------
`/actions/` contains files that determine what happens when something "happens":
for instance, when someone favourites or repeats a notice.  Code that is
related to a "happening" should go here.

`/classes/` contains abstract definitions of certain "things" in the codebase
such as a user or notice.  If you're making a new "thing", it goes here.

`/lib/` is basically the back-end.  Actions will call something in here to get
stuff done usually, which in turn will probably manipulate information stored
in one or more records represented by a class.

`/extlib/` is where external libraries are located.  If you include a new
external library, it goes here.

`/plugins/` This is a great way to modularize your own new features.  If you want
to create new core features for GNU social, it is probably best to create a
module unless you absolutely must override or modify the core behaviours.


code:
Submission Checklist
================================================================================
This document serves as a handy checklist for submitted merges and patches to
the GNU social project. Following it isn't a gaurantee a patch will be accepted,
but it will help you avoid common problems.

1. Ensure all code control paths in all functions return a value.

2. Ensure all exceptions are trapped in an exception class, or minimally,
   written to the log with common_log

3. Ensure the coding format standards are adhered to (see coding_standards.md)

4. Ensure that any new class that deals in public data has a corresponding new
   API endpoint.

5. Ensure that all new API endpoints sanitize inputs and outputs properly.

6. Ensure that your version of the code works with PHP 7 on a standard
   LAMP and LEMP stack (Linux+Apache+MariaDB+PHP and Linux+nginx+MariaDB+PHP)

7. If implementing new database functions, ensure they work with MariaDB
   and postgreSQL.

8. Ensure all data that federates does so properly and has mechanisms to
   catch and accomodate for federation transmission failure.

9. Ensure that nothing is left in an error state when it is avoidable.

10. Ensure that all code submitted is properly documented.

11. Ensure that there are no PHP Strict Standards or Parse errors in the code.