work rust
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 14 Dec 2025 21:32:58 +0000 (13:32 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 14 Dec 2025 21:32:58 +0000 (13:32 -0800)
rust/pspp/src/format.rs
rust/pspp/src/output/drivers/text.rs
rust/pspp/src/output/pivot.rs
rust/pspp/src/output/pivot/look.rs
rust/pspp/src/output/pivot/look_xml.rs
rust/pspp/src/output/pivot/output.rs
rust/pspp/src/output/pivot/value.rs
rust/pspp/src/output/table.rs

index 8a0afb38985fa4d4d41afea071520577b5e6c3d5..bc61eacc04ab1b2a69eb2007715896bdf8c3eb13 100644 (file)
@@ -1196,7 +1196,7 @@ impl Affix {
         self.s.len().checked_sub(self.width).unwrap()
     }
 
-    fn display(&self, escape: char) -> DisplayAffix<'_> {
+    fn display(&self, escape: char) -> impl Display {
         DisplayAffix {
             affix: self.s.as_str(),
             escape,
index 333a9029086a537b2a8e371324dbb3f8a4148a5e..5827fef5f842570d8953cf74cebe9c45b97b9797 100644 (file)
@@ -332,7 +332,9 @@ static UNICODE_BOX: LazyLock<BoxChars> = LazyLock::new(|| {
 });
 
 impl PivotTable {
-    pub fn display(&self) -> DisplayPivotTable<'_> {
+    /// Returns a object that will format the pivot table as text, with Unicode
+    /// box drawing characters.
+    pub fn display(&self) -> impl Display {
         DisplayPivotTable::new(self)
     }
 }
@@ -343,7 +345,7 @@ impl Display for PivotTable {
     }
 }
 
-pub struct DisplayPivotTable<'a> {
+struct DisplayPivotTable<'a> {
     pt: &'a PivotTable,
 }
 
index f72874ed331b769e60eceefd3010b332c0977312..5eb53af11963f51be1a7e29ba1468ac8cdcc7dda 100644 (file)
@@ -65,7 +65,7 @@ use crate::{
     format::{F40, F40_2, F40_3, Format, PCT40_1, Settings as FormatSettings},
     output::pivot::{
         look::{Look, Sizing},
-        value::{BareValue, DisplayValue, IntoValueOptions, Value, ValueOptions},
+        value::{BareValue, Value, ValueOptions},
     },
     settings::{Settings, Show},
     variable::Variable,
@@ -473,10 +473,12 @@ impl PivotTable {
         }
     }
 
+    /// Transposes row and columns.
     pub fn transpose(&mut self) {
         self.axes.swap(Axis3::X, Axis3::Y);
     }
 
+    /// Returns an iterator through dimensions on the given `axis`.
     pub fn axis_dimensions(
         &self,
         axis: Axis3,
@@ -499,6 +501,20 @@ impl PivotTable {
         }
         None
     }
+
+    /// Moves dimension with index `dim_index` from its current axis to
+    /// `new_axis` in position `new_position`.
+    ///
+    /// `dim_index` is an overall dimension index, in the order passed to
+    /// [PivotTable::new] and returned by [PivotTable::dimensions].  This method
+    /// doesn't change these indexes.
+    ///
+    /// `new_position` is an index within `new_axis`, in the range `0..n` where
+    /// `n` is the final number of dimensions along that axis.
+    ///
+    /// # Panic
+    ///
+    /// Panics if `dim_index` or `new_position` is outside the valid range.
     pub fn move_dimension(&mut self, dim_index: usize, new_axis: Axis3, new_position: usize) {
         let (old_axis, old_position) = self.find_dimension(dim_index).unwrap();
         if old_axis == new_axis && old_position == new_position {
@@ -534,13 +550,13 @@ impl PivotTable {
     }
 }
 
-impl IntoValueOptions for &PivotTable {
-    fn into_value_options(self) -> ValueOptions {
+impl From<&PivotTable> for ValueOptions {
+    fn from(value: &PivotTable) -> Self {
         ValueOptions {
-            show_values: self.style.show_values,
-            show_variables: self.style.show_variables,
-            small: self.style.small,
-            footnote_marker_type: self.style.look.footnote_marker_type,
+            show_values: value.style.show_values,
+            show_variables: value.style.show_variables,
+            small: value.style.small,
+            footnote_marker_type: value.style.look.footnote_marker_type,
         }
     }
 }
@@ -577,15 +593,28 @@ pub struct Dimension {
     pub hide_all_labels: bool,
 }
 
+/// A vector of references to [Group]s.
+///
+/// Used to represent a sequence of groups along a [Path].  This is a [SmallVec]
+/// because groups are usually not deeply nested.
 pub type GroupVec<'a> = SmallVec<[&'a Group; 4]>;
+
+/// A path from the root of a [Dimension] to a [Leaf].
 pub struct Path<'a> {
+    /// Groups along the path.
     groups: GroupVec<'a>,
+
+    /// The leaf.
+    ///
+    /// This is a child of the last [Group].
     leaf: &'a Leaf,
 }
 
+/// Group indexes visited along a [Path].
 pub type IndexVec = SmallVec<[usize; 4]>;
 
 impl Dimension {
+    /// Constructs a new [Dimension] with the given `root`.
     pub fn new(root: Group) -> Self {
         Dimension {
             presentation_order: (0..root.len()).collect(),
@@ -594,36 +623,47 @@ impl Dimension {
         }
     }
 
+    /// Returns this dimension with [Dimension::hide_all_labels] set to true.
+    pub fn with_all_labels_hidden(self) -> Self {
+        Self {
+            hide_all_labels: true,
+            ..self
+        }
+    }
+
+    /// Returns true if [Dimension] has no leaf categories.
+    ///
+    /// A dimension without leaf categories might still contain a hierarchy of
+    /// groups, just none of them with leaves.
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
 
-    /// Returns the number of (leaf) categories in this dimension.
+    /// Returns the number of leaf categories in this dimension.
     pub fn len(&self) -> usize {
         self.root.len()
     }
 
+    /// Returns the leaf with the given 0-based `index`, or `None` if `index >=
+    /// self.len()`.
     pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
         self.root.nth_leaf(index)
     }
 
+    /// Returns the path to the leaf with the given 0-based `index`, or `None`
+    /// if `index >= self.len()`.
     pub fn leaf_path(&self, index: usize) -> Option<Path<'_>> {
         self.root.leaf_path(index, SmallVec::new())
     }
 
+    /// Returns the series of group child indexes followed to the leaf with the
+    /// given 0-based `index`, or `None` if `index >= self.len()`.
     pub fn index_path(&self, index: usize) -> Option<IndexVec> {
         self.root.index_path(index, SmallVec::new())
     }
-
-    pub fn with_all_labels_hidden(self) -> Self {
-        Self {
-            hide_all_labels: true,
-            ..self
-        }
-    }
 }
 
-/// Specifies a [Category] within a [Group].
+/// Specifies how to find a [Category] within a [Group].
 #[derive(Copy, Clone, Debug)]
 pub struct CategoryLocator {
     /// The index of the leaf to start from.
@@ -635,6 +675,8 @@ pub struct CategoryLocator {
 }
 
 impl CategoryLocator {
+    /// Constructs a `CategoryLocator` for the leaf with the given 0-based
+    /// index.
     pub fn new_leaf(leaf_index: usize) -> Self {
         Self {
             leaf_index,
@@ -642,6 +684,7 @@ impl CategoryLocator {
         }
     }
 
+    /// Returns a `CategoryLocator` for the parent category of this one.
     pub fn parent(&self) -> Self {
         Self {
             leaf_index: self.leaf_index,
@@ -649,32 +692,41 @@ impl CategoryLocator {
         }
     }
 
+    /// If this is a leaf, returns its 0-based index; otherwise, returns `None`.
     pub fn as_leaf(&self) -> Option<usize> {
         (self.level == 0).then_some(self.leaf_index)
     }
 }
 
+/// A group of categories within a dimension.
 #[derive(Clone, Debug, Serialize)]
 pub struct Group {
+    /// Number of leaves contained by this group.
     #[serde(skip)]
     len: usize,
+
+    /// The label displayed for the group.
     pub name: Box<Value>,
 
+    /// Whether to show the label.
+    pub show_label: bool,
+
     /// The child categories.
     ///
     /// A group usually has multiple children, but it is allowed to have
     /// only one or even (pathologically) none.
     pub children: Vec<Category>,
-
-    /// Whether to show the group's label.
-    pub show_label: bool,
 }
 
 impl Group {
+    /// Constructs a new `Group` with the given name.  The group initially has
+    /// no children.
     pub fn new(name: impl Into<Value>) -> Self {
         Self::with_capacity(name, 0)
     }
 
+    /// Constructs a new `Group` with the given name and initial child
+    /// `capacity`.  The group initially has no children.
     pub fn with_capacity(name: impl Into<Value>, capacity: usize) -> Self {
         Self {
             len: 0,
@@ -684,6 +736,7 @@ impl Group {
         }
     }
 
+    /// Appends `child` to the group's categories.
     pub fn push(&mut self, child: impl Into<Category>) {
         let mut child = child.into();
         if let Some(group) = child.as_group_mut() {
@@ -693,11 +746,13 @@ impl Group {
         self.children.push(child);
     }
 
+    /// Returns this group with the given `child` appended to its categories.
     pub fn with(mut self, child: impl Into<Category>) -> Self {
         self.push(child);
         self
     }
 
+    /// Returns this group with the given `children` appended to its categories.
     pub fn with_multiple<C>(mut self, children: impl IntoIterator<Item = C>) -> Self
     where
         C: Into<Category>,
@@ -706,16 +761,20 @@ impl Group {
         self
     }
 
+    /// Returns this group with `show_label` set to true.
     pub fn with_label_shown(self) -> Self {
         self.with_show_label(true)
     }
 
+    /// Returns this group with `show_label` set as specified.
     pub fn with_show_label(mut self, show_label: bool) -> Self {
         self.show_label = show_label;
         self
     }
 
-    pub fn nth_leaf(&self, mut index: usize) -> Option<&Leaf> {
+    /// Returns the leaf with the given 0-based `index`, or `None` if `index >=
+    /// self.len()`.
+    fn nth_leaf(&self, mut index: usize) -> Option<&Leaf> {
         for child in &self.children {
             let len = child.len();
             if index < len {
@@ -726,7 +785,10 @@ impl Group {
         None
     }
 
-    pub fn leaf_path<'a>(&'a self, mut index: usize, mut groups: GroupVec<'a>) -> Option<Path<'a>> {
+    /// Returns the path to the leaf with the given 0-based `index`, or `None`
+    /// if `index >= self.len()`.  `groups` must be the groups already traversed
+    /// to arrive at this group.
+    fn leaf_path<'a>(&'a self, mut index: usize, mut groups: GroupVec<'a>) -> Option<Path<'a>> {
         for child in &self.children {
             let len = child.len();
             if index < len {
@@ -756,9 +818,10 @@ impl Group {
         Some(path)
     }
 
-    /// Returns `None` if `locator` is invalid (that is, if `locator.leaf_idx >=
-    /// self.len` or `locator.level` is greater than the depth of the leaf) or
-    /// if `locator` designates `self`.
+    /// Returns the category corresponding to `locator`.  Returns `None` if
+    /// `locator` is invalid (that is, if `locator.leaf_idx >= self.len` or
+    /// `locator.level` is greater than the depth of the leaf) or if `locator`
+    /// designates `self`.
     pub fn category(&self, locator: CategoryLocator) -> Option<&Category> {
         let path = self.locator_path(locator)?;
         let mut this = &self.children[*path.get(0)?];
@@ -768,6 +831,10 @@ impl Group {
         Some(this)
     }
 
+    /// Returns the category corresponding to `locator`.  Returns `None` if
+    /// `locator` is invalid (that is, if `locator.leaf_idx >= self.len` or
+    /// `locator.level` is greater than the depth of the leaf) or if `locator`
+    /// designates `self`.
     pub fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
         let path = self.locator_path(locator)?;
         let mut this = &mut self.children[*path.get(0)?];
@@ -777,14 +844,17 @@ impl Group {
         Some(this)
     }
 
+    /// Returns the number of leaves contained within this group.
     pub fn len(&self) -> usize {
         self.len
     }
 
+    /// Returns true if this group contains no leaves.
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
 
+    /// Returns this group's label.
     pub fn name(&self) -> &Value {
         &self.name
     }
@@ -813,24 +883,30 @@ where
 pub struct Footnotes(Vec<Arc<Footnote>>);
 
 impl Footnotes {
+    /// Constructs a new, empty collection of footnotes.
     pub fn new() -> Self {
         Self::default()
     }
 
-    pub fn push(&mut self, footnote: Footnote) -> Arc<Footnote> {
-        let footnote = Arc::new(footnote.with_index(self.0.len()));
-        self.0.push(footnote.clone());
-        footnote
+    /// Returns the number of footnotes in the collection.
+    pub fn len(&self) -> usize {
+        self.0.len()
     }
 
+    /// Returns true if the collection contains no footnotes.
     pub fn is_empty(&self) -> bool {
         self.0.is_empty()
     }
 
-    pub fn len(&self) -> usize {
-        self.0.len()
+    /// Adds `footnote` to the collection.
+    pub fn push(&mut self, footnote: Footnote) -> Arc<Footnote> {
+        let footnote = Arc::new(footnote.with_index(self.0.len()));
+        self.0.push(footnote.clone());
+        footnote
     }
 
+    /// Returns the footnote with 0-based index `index`, or `None` if `index >=
+    /// self.len()`.
     pub fn get(&self, index: usize) -> Option<&Arc<Footnote>> {
         self.0.get(index)
     }
@@ -865,13 +941,17 @@ impl FromIterator<Footnote> for Footnotes {
     }
 }
 
+/// A leaf category within a [Group] and ultimately within a [Dimension].
 #[derive(Clone, Debug)]
-pub struct Leaf(Box<Value>);
+pub struct Leaf(pub Box<Value>);
 
 impl Leaf {
+    /// Constructs a new `Leaf` with the given label.
     pub fn new(name: Value) -> Self {
         Self(Box::new(name))
     }
+
+    /// Returns the leaf's label.
     pub fn name(&self) -> &Value {
         &self.0
     }
@@ -888,27 +968,45 @@ impl Serialize for Leaf {
 
 /// Pivot result classes.
 ///
-/// These are used to mark [Leaf] categories as having particular types of data,
-/// to set their numeric formats.
+/// Used by [PivotTable::insert_number].
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Class {
-    Other,
+    /// An integer value.
     Integer,
+    /// A correlation value.
     Correlations,
+    /// A measure of significance.
     Significance,
+    /// A percentage.
     Percent,
+    /// A residual value.
     Residual,
+    /// A count.
+    ///
+    /// With a weighted dataset, these are by default displayed with the weight
+    /// variable's format.
     Count,
+    /// A value that doesn't fit in one of the other categories.
+    Other,
 }
 
 /// A leaf category or a group of them.
 #[derive(Clone, Debug, Serialize)]
 pub enum Category {
-    Group(Group),
-    Leaf(Leaf),
+    /// A group.
+    Group(
+        /// The group.
+        Group,
+    ),
+    /// A leaf.
+    Leaf(
+        /// The leaf.
+        Leaf,
+    ),
 }
 
 impl Category {
+    /// Returns the [Group] in this `Category`, if there is one.
     pub fn as_group(&self) -> Option<&Group> {
         match self {
             Category::Group(group) => Some(group),
@@ -916,6 +1014,8 @@ impl Category {
         }
     }
 
+    /// Returns the [Group] in this `Category`, if there is one, for
+    /// modification.
     pub fn as_group_mut(&mut self) -> Option<&mut Group> {
         match self {
             Category::Group(group) => Some(group),
@@ -923,6 +1023,7 @@ impl Category {
         }
     }
 
+    /// Returns the [Leaf] in this `Category`, if there is one.
     pub fn as_leaf(&self) -> Option<&Leaf> {
         match self {
             Category::Leaf(leaf) => Some(leaf),
@@ -930,6 +1031,8 @@ impl Category {
         }
     }
 
+    /// Returns the [Leaf] in this `Category`, if there is one, for
+    /// modification.
     pub fn as_leaf_mut(&mut self) -> Option<&mut Leaf> {
         match self {
             Category::Leaf(leaf) => Some(leaf),
@@ -937,6 +1040,7 @@ impl Category {
         }
     }
 
+    /// Returns the category's label.
     pub fn name(&self) -> &Value {
         match self {
             Category::Group(group) => &group.name,
@@ -944,6 +1048,7 @@ impl Category {
         }
     }
 
+    /// Returns the category's label, for modification.
     pub fn name_mut(&mut self) -> &mut Value {
         match self {
             Category::Group(group) => &mut group.name,
@@ -951,10 +1056,24 @@ impl Category {
         }
     }
 
+    /// Returns true if this category's label should be shown.
+    ///
+    /// A leaf category's label is always shown, unless
+    /// [Dimension::hide_all_labels] is true.
+    pub fn show_label(&self) -> bool {
+        match self {
+            Category::Group(group) => group.show_label,
+            Category::Leaf(_) => true,
+        }
+    }
+    /// Returns true if the category contains no leaves.
+    ///
+    /// If this is a leaf category, this always returns false.
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
 
+    /// Returns the number of leaves that this category contains.
     pub fn len(&self) -> usize {
         match self {
             Category::Group(group) => group.len,
@@ -962,7 +1081,7 @@ impl Category {
         }
     }
 
-    pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
+    fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
         match self {
             Category::Group(group) => group.nth_leaf(index),
             Category::Leaf(leaf) if index == 0 => Some(leaf),
@@ -970,7 +1089,7 @@ impl Category {
         }
     }
 
-    pub fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option<Path<'a>> {
+    fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option<Path<'a>> {
         match self {
             Category::Group(group) => group.leaf_path(index, groups),
             Category::Leaf(leaf) if index == 0 => Some(Path { groups, leaf }),
@@ -994,7 +1113,7 @@ impl Category {
 
     /// Returns `None` if `locator` is invalid (that is, if `locator.leaf_idx >=
     /// self.len` or `locator.level` is greater than the depth of the leaf).
-    pub fn category(&self, locator: CategoryLocator) -> Option<&Category> {
+    fn category(&self, locator: CategoryLocator) -> Option<&Category> {
         let mut this = self;
         for index in this.locator_path(locator)? {
             this = &this.as_group().unwrap().children[index];
@@ -1002,20 +1121,13 @@ impl Category {
         Some(this)
     }
 
-    pub fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
+    fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
         let mut this = self;
         for index in this.locator_path(locator)? {
             this = &mut this.as_group_mut().unwrap().children[index];
         }
         Some(this)
     }
-
-    pub fn show_label(&self) -> bool {
-        match self {
-            Category::Group(group) => group.show_label,
-            Category::Leaf(_) => true,
-        }
-    }
 }
 
 impl From<Group> for Category {
@@ -1064,15 +1176,19 @@ impl From<&String> for Category {
 #[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(rename_all = "snake_case")]
 pub enum Axis2 {
+    /// X axis.
     X,
+    /// Y axis.
     Y,
 }
 
 impl Axis2 {
+    /// Returns a map from [Axis2::X] to `x` and from [Axis2::Y] to `y`.
     pub fn new_enum<T>(x: T, y: T) -> EnumMap<Axis2, T> {
         EnumMap::from_array([x, y])
     }
 
+    /// Returns the axis's name, in lowercase, as a static string.
     pub fn as_str(&self) -> &'static str {
         match self {
             Axis2::X => "x",
@@ -1120,6 +1236,7 @@ impl TryFrom<Axis3> for Axis2 {
 pub struct Coord2(pub EnumMap<Axis2, isize>);
 
 impl Coord2 {
+    /// Constructs a new `Coord2` with the given `x` and `y` coordinates.
     pub fn new(x: isize, y: isize) -> Self {
         use Axis2::*;
         Self(enum_map! {
@@ -1128,6 +1245,8 @@ impl Coord2 {
         })
     }
 
+    /// Constructs a new `Coord2` with coordinate `az` on axis `a` and
+    /// coordinate `bz` on the other axis.
     pub fn for_axis((a, az): (Axis2, isize), bz: isize) -> Self {
         let mut coord = Self::default();
         coord[a] = az;
@@ -1135,6 +1254,7 @@ impl Coord2 {
         coord
     }
 
+    /// Constructs a new `Coord2` with coordinates from function `f`.
     pub fn from_fn<F>(f: F) -> Self
     where
         F: FnMut(Axis2) -> isize,
@@ -1142,14 +1262,17 @@ impl Coord2 {
         Self(EnumMap::from_fn(f))
     }
 
+    /// Returns the X coordinate.
     pub fn x(&self) -> isize {
         self.0[Axis2::X]
     }
 
+    /// Returns the Y coordinate.
     pub fn y(&self) -> isize {
         self.0[Axis2::Y]
     }
 
+    /// Returns the coordinate on the given `axis`.
     pub fn get(&self, axis: Axis2) -> isize {
         self.0[axis]
     }
@@ -1175,19 +1298,24 @@ impl IndexMut<Axis2> for Coord2 {
     }
 }
 
+/// A 2-dimensional rectangle.
 #[derive(Clone, Debug, Default)]
-pub struct Rect2(pub EnumMap<Axis2, Range<isize>>);
+pub struct Rect2(
+    /// The range along each axis.
+    pub EnumMap<Axis2, Range<isize>>,
+);
 
 impl Rect2 {
+    /// Constructs a new `Rect2` that covers the given X and Y ranges.
     pub fn new(x_range: Range<isize>, y_range: Range<isize>) -> Self {
         Self(enum_map! {
             Axis2::X => x_range.clone(),
             Axis2::Y => y_range.clone(),
         })
     }
-    pub fn for_cell(cell: Coord2) -> Self {
-        Self::new(cell.x()..cell.x() + 1, cell.y()..cell.y() + 1)
-    }
+
+    /// Construct a new `Rect2` that covers `a_range` along axis `a` and
+    /// `b_range` along the other axis.
     pub fn for_ranges((a, a_range): (Axis2, Range<isize>), b_range: Range<isize>) -> Self {
         let b = !a;
         let mut ranges = EnumMap::default();
@@ -1195,19 +1323,27 @@ impl Rect2 {
         ranges[b] = b_range;
         Self(ranges)
     }
+
+    /// Returns the top-left corner of the rectangle.
     pub fn top_left(&self) -> Coord2 {
         use Axis2::*;
         Coord2::new(self[X].start, self[Y].start)
     }
+
+    /// Construct a new `Rect2` that covers the ranges returned by `f`.
     pub fn from_fn<F>(f: F) -> Self
     where
         F: FnMut(Axis2) -> Range<isize>,
     {
         Self(EnumMap::from_fn(f))
     }
+
+    /// Shifts the rectangle by `offset` along its axes.
     pub fn translate(self, offset: Coord2) -> Rect2 {
         Self::from_fn(|axis| self[axis].start + offset[axis]..self[axis].end + offset[axis])
     }
+
+    /// Returns true if the rectangle is empty.
     pub fn is_empty(&self) -> bool {
         self[Axis2::X].is_empty() || self[Axis2::Y].is_empty()
     }
@@ -1233,6 +1369,7 @@ impl IndexMut<Axis2> for Rect2 {
     }
 }
 
+/// What to show in a footnote marker.
 #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
 #[serde(rename_all = "camelCase")]
 pub enum FootnoteMarkerType {
@@ -1244,6 +1381,7 @@ pub enum FootnoteMarkerType {
     Numeric,
 }
 
+/// How to show a footnote marker.
 #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
 #[serde(rename_all = "camelCase")]
 pub enum FootnoteMarkerPosition {
@@ -1309,6 +1447,7 @@ pub struct PivotTableStyle {
     /// [DatumValue::honor_small]: value::DatumValue::honor_small
     pub small: f64,
 
+    /// The format to use for weight and count variables.
     pub weight_format: Format,
 }
 
@@ -1333,30 +1472,43 @@ impl Default for PivotTableStyle {
 }
 
 impl PivotTableStyle {
-    fn with_look(self, look: Arc<Look>) -> Self {
+    /// Returns this style with the given `look`.
+    pub fn with_look(self, look: Arc<Look>) -> Self {
         Self { look, ..self }
     }
-    fn with_show_values(self, show_values: Option<Show>) -> Self {
+
+    /// Returns this style with the given `show_values`.
+    pub fn with_show_values(self, show_values: Option<Show>) -> Self {
         Self {
             show_values,
             ..self
         }
     }
-    fn with_show_variables(self, show_variables: Option<Show>) -> Self {
+
+    /// Returns this style with the given `show_variables`.
+    pub fn with_show_variables(self, show_variables: Option<Show>) -> Self {
         Self {
             show_variables,
             ..self
         }
     }
-    fn with_show_title(self, show_title: bool) -> Self {
+
+    /// Returns this style with the given `show_title`.
+    pub fn with_show_title(self, show_title: bool) -> Self {
         Self { show_title, ..self }
     }
-    fn with_show_caption(self, show_caption: bool) -> Self {
+
+    /// Returns this style with the given `show_caption`.
+    pub fn with_show_caption(self, show_caption: bool) -> Self {
         Self {
             show_caption,
             ..self
         }
     }
+
+    /// Returns a mutable reference to this style's [Look].
+    ///
+    /// This unshares the [Arc] used to refer to the [Look].
     pub fn look_mut(&mut self) -> &mut Look {
         Arc::make_mut(&mut self.look)
     }
@@ -1425,6 +1577,7 @@ pub struct PivotTableMetadata {
 }
 
 impl PivotTableMetadata {
+    /// Return this metadata with the given `subtype`.
     pub fn with_subtype(self, subtype: impl Into<Value>) -> Self {
         Self {
             subtype: Some(Box::new(subtype.into())),
@@ -1433,8 +1586,15 @@ impl PivotTableMetadata {
     }
 }
 
+/// A pivot table.
+///
+/// Pivot tables are PSPP's primary form of output.  They are analogous to the
+/// pivot tables you might be familiar with from spreadsheets and databases.
+/// See <https://en.wikipedia.org/wiki/Pivot_table> for a brief introduction to
+/// the overall concept of a pivot table.
 #[derive(Clone, Debug, Serialize)]
 pub struct PivotTable {
+    /// Style.
     pub style: PivotTableStyle,
 
     /// Current layer indexes, with `axes[Axis3::Z].dimensions.len()` elements.
@@ -1444,10 +1604,19 @@ pub struct PivotTable {
     /// corresponding leaf.
     layer: Vec<usize>,
 
+    /// Metadata.
     pub metadata: PivotTableMetadata,
+
+    /// Footnotes.
     pub footnotes: Footnotes,
+
+    /// Dimensions.
     dimensions: Vec<Dimension>,
+
+    /// Axes.
     axes: EnumMap<Axis3, Axis>,
+
+    /// Data.
     cells: HashMap<usize, Value>,
 }
 
@@ -1465,7 +1634,9 @@ impl Default for PivotTable {
     }
 }
 
+/// A type that can calculate the index into a [PivotTable]'s data hashmap.
 pub trait CellIndex {
+    /// Given the pivot table's `dimensions`, returns an index.
     fn cell_index<I>(self, dimensions: I) -> usize
     where
         I: ExactSizeIterator<Item = usize>;
@@ -1489,7 +1660,11 @@ where
     }
 }
 
-pub struct PrecomputedIndex(pub usize);
+/// A precomputed index.
+pub struct PrecomputedIndex(
+    /// The index.
+    pub usize,
+);
 
 impl CellIndex for PrecomputedIndex {
     fn cell_index<I>(self, _dimensions: I) -> usize
@@ -1511,16 +1686,32 @@ where
     }
 }
 
+/// A footnote in a [PivotTable].
+///
+/// A footnote is attached directly to a [Value], but it will only be displayed
+/// correctly if it is also added to [PivotTable::footnotes] for its pivot
+/// table.
 #[derive(Clone, Debug, Serialize, PartialEq)]
 pub struct Footnote {
+    /// The index within [Footnotes].
     #[serde(skip)]
     index: usize,
+
+    /// The footnote text.
     pub content: Box<Value>,
+
+    /// The footnote marker.
+    ///
+    /// This is usually `None`, in which case [FootnoteMarkerType] determines
+    /// the default marker.
     pub marker: Option<Box<Value>>,
+
+    /// Whether to show the footnote.
     pub show: bool,
 }
 
 impl Footnote {
+    /// Constructs a new footnote.
     pub fn new(content: impl Into<Value>) -> Self {
         Self {
             index: 0,
@@ -1529,35 +1720,44 @@ impl Footnote {
             show: true,
         }
     }
+
+    /// Returns the footnote with the given optional marker.
     pub fn with_marker(self, marker: Option<Value>) -> Self {
         Self {
             marker: marker.map(Box::new),
             ..self
         }
     }
+
+    /// Returns the footnote with the given marker.
     pub fn with_some_marker(self, marker: impl Into<Value>) -> Self {
         Self::with_marker(self, Some(marker.into()))
     }
 
+    /// Return the footnote with the given `show`.
     pub fn with_show(self, show: bool) -> Self {
         Self { show, ..self }
     }
 
+    /// Return the footnote with the given `index`.
     pub fn with_index(self, index: usize) -> Self {
         Self { index, ..self }
     }
 
-    pub fn display_marker(&self, options: impl IntoValueOptions) -> DisplayMarker<'_> {
+    /// Returns an object for formatting the footnote's marker.
+    pub fn display_marker(&self, options: impl Into<ValueOptions>) -> impl Display {
         DisplayMarker {
             footnote: self,
-            options: options.into_value_options(),
+            options: options.into(),
         }
     }
 
-    pub fn display_content(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
+    /// Returns an object for formatting the footnote's text.
+    pub fn display_content(&self, options: impl Into<ValueOptions>) -> impl Display {
         self.content.display(options)
     }
 
+    /// Returns the footnote's index.
     pub fn index(&self) -> usize {
         self.index
     }
@@ -1569,7 +1769,7 @@ impl Default for Footnote {
     }
 }
 
-pub struct DisplayMarker<'a> {
+struct DisplayMarker<'a> {
     footnote: &'a Footnote,
     options: ValueOptions,
 }
@@ -1624,18 +1824,25 @@ impl Display for Display26Adic {
     }
 }
 
+/// An entry in a metadata table.
 pub struct MetadataEntry {
+    /// The label for the entry.
     pub name: Value,
+
+    /// The value for the entry.
     pub value: MetadataValue,
 }
 
 impl MetadataEntry {
+    /// Constructs a new metadata entry with the given name and value.
     pub fn new(name: impl Into<Value>, value: MetadataValue) -> Self {
         Self {
             name: name.into(),
             value,
         }
     }
+
+    /// Converts the metadata entry into a pivot table.
     pub fn into_pivot_table(self) -> PivotTable {
         let mut data = Vec::new();
         let group = match self.visit(&mut data) {
@@ -1662,12 +1869,22 @@ impl MetadataEntry {
     }
 }
 
+/// A value in a metadata table.
 pub enum MetadataValue {
-    Leaf(Value),
-    Group(Vec<MetadataEntry>),
+    /// A value.
+    Leaf(
+        /// The value.
+        Value,
+    ),
+    /// A nested group of entries.
+    Group(
+        /// The entries.
+        Vec<MetadataEntry>,
+    ),
 }
 
 impl MetadataValue {
+    /// Construct a new "leaf" metadata value.
     pub fn new_leaf(value: impl Into<Value>) -> Self {
         Self::Leaf(value.into())
     }
index 7157377d763c39fed3f85f5bdcad1bca25f9477f..3528a7a4dadf650db4a2f235c1ff2d1ac955f305 100644 (file)
@@ -918,7 +918,7 @@ impl Color {
     }
 
     /// Displays opaque colors as `#rrggbb` and others as `rgb(r, g, b, alpha)`.
-    pub fn display_css(&self) -> ColorDisplayCss {
+    pub fn display_css(&self) -> impl Display {
         ColorDisplayCss(*self)
     }
 
@@ -1005,7 +1005,7 @@ impl<'de> Deserialize<'de> for Color {
 /// A structure for formatting a [Color] in a CSS-compatible format.
 ///
 /// See [Color::display_css].
-pub struct ColorDisplayCss(Color);
+struct ColorDisplayCss(Color);
 
 impl Display for ColorDisplayCss {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
index 429b59538c1af3fb4d76706875eed645ccc2ea78..6215acd5587177082e53afbfcc8d4a204f8b8c88 100644 (file)
@@ -31,6 +31,7 @@ use crate::{
 };
 use thiserror::Error as ThisError;
 
+/// An XML TableLook.
 #[derive(Clone, Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 pub struct TableProperties {
@@ -348,6 +349,7 @@ struct PrintingProperties {
     continuation_text_at_top: bool,
 }
 
+/// A length.
 #[derive(Copy, Clone, Default, PartialEq)]
 pub struct Length(
     /// In inches.
@@ -355,15 +357,19 @@ pub struct Length(
 );
 
 impl Length {
+    /// Returns the length in 1/96" units.
     pub fn as_px_f64(self) -> f64 {
         self.0 * 96.0
     }
+    /// Returns the length in 1/96" units.
     pub fn as_px_i32(self) -> i32 {
         num::cast(self.as_px_f64() + 0.5).unwrap_or_default()
     }
+    /// Returns the length in 1/72" units.
     pub fn as_pt_f64(self) -> f64 {
         self.0 * 72.0
     }
+    /// Returns the length in 1/72" units.
     pub fn as_pt_i32(self) -> i32 {
         num::cast(self.as_pt_f64() + 0.5).unwrap_or_default()
     }
index 25bf952301e9b636fff7855ad8a989889084342c..6b36ececc117eae0727e65e0123a0ce5c0b83b45 100644 (file)
@@ -30,7 +30,7 @@ use crate::output::{
 use crate::output::pivot::{
     Axis2, Axis3, Dimension, PivotTable,
     look::{Area, Border, BorderStyle, BoxBorder, Color, RowColBorder, Stroke},
-    value::{IntoValueOptions, Value},
+    value::Value,
 };
 
 /// All of the combinations of dimensions along an axis.
@@ -147,7 +147,7 @@ impl PivotTable {
             CellPos::new(0, 0),
             self.style.look.areas.clone(),
             self.borders(false),
-            self.into_value_options(),
+            self,
         );
         for (y, row) in rows.enumerate() {
             table.put(
@@ -179,6 +179,10 @@ impl PivotTable {
         })
     }
 
+    /// Constructs a [Table] for the body of this `PivotTable` for the layer
+    /// with the specified indexes.  `printing` specifies whether the table is
+    /// for printing or screen display (grid lines are only enabled for
+    /// printing).
     pub fn output_body(&self, layer_indexes: &[usize], printing: bool) -> Table {
         let headings = EnumMap::from_fn(|axis| Headings::new(self, axis, layer_indexes));
 
@@ -192,7 +196,7 @@ impl PivotTable {
             stub,
             self.style.look.areas.clone(),
             self.borders(printing),
-            self.into_value_options(),
+            self,
         );
 
         for h in [Axis2::X, Axis2::Y] {
@@ -246,6 +250,8 @@ impl PivotTable {
         body
     }
 
+    /// Constructs a [Table] for this `PivotTable`'s title.  Returns `None` if
+    /// the table doesn't have a title.
     pub fn output_title(&self) -> Option<Table> {
         Some(self.create_aux_table3(
             Area::Title,
@@ -253,6 +259,8 @@ impl PivotTable {
         ))
     }
 
+    /// Constructs a [Table] for this `PivotTable`'s layer values.  Returns
+    /// `None` if the table doesn't have layers.
     pub fn output_layers(&self, layer_indexes: &[usize]) -> Option<Table> {
         let mut layers = Vec::new();
         for (dimension, &layer_index) in zip(
@@ -271,6 +279,8 @@ impl PivotTable {
         self.create_aux_table_if_nonempty(Area::Layers, layers.into_iter())
     }
 
+    /// Constructs a [Table] for this `PivotTable`'s caption.  Returns `None` if
+    /// the table doesn't have a caption.
     pub fn output_caption(&self) -> Option<Table> {
         Some(self.create_aux_table3(
             Area::Caption,
@@ -278,6 +288,8 @@ impl PivotTable {
         ))
     }
 
+    /// Constructs a [Table] for this `PivotTable`'s footnotes.  Returns `None`
+    /// if the table doesn't have footnotes.
     pub fn output_footnotes(&self, footnotes: &[Arc<Footnote>]) -> Option<Table> {
         self.create_aux_table_if_nonempty(
             Area::Footer,
@@ -291,6 +303,8 @@ impl PivotTable {
         )
     }
 
+    /// Constructs [OutputTables] for this `PivotTable`, for the specified
+    /// layer, formatted for screen display or printing as specified.
     pub fn output(&self, layer_indexes: &[usize], printing: bool) -> OutputTables {
         // Produce most of the tables.
         let title = self.style.show_title.then(|| self.output_title()).flatten();
@@ -355,15 +369,30 @@ impl PivotTable {
     }
 }
 
+/// [Table]s for outputting a layer of a [PivotTable].
 pub struct OutputTables {
+    /// Title table, if any.
     pub title: Option<Table>,
+    /// Layers table, if any.
     pub layers: Option<Table>,
+    /// Table body.
     pub body: Table,
+    /// Table caption, if any.
     pub caption: Option<Table>,
+    /// Footnotes, if any.
     pub footnotes: Option<Table>,
 }
 
 impl Path<'_> {
+    /// Gets the label to be displayed for this path to a leaf within a heading
+    /// block with the given `height`.  Returns both the label and the range of
+    /// rows within the heading block that displays the label.
+    ///
+    /// A path to a leaf that contains `n` groups must be displayed in a heading
+    /// block with at least `n + 1` rows.  Within a heading block with `height`
+    /// rows, the groups are displayed in rows `0..n`, and the leaf is displayed
+    /// in rows `n..height`.  Thus, each group is displayed in exactly one row,
+    /// but the leaf can span multiple rows.
     pub fn get(&self, y: usize, height: usize) -> (&Value, Range<usize>) {
         debug_assert!(height > self.groups.len());
         if let Some(group) = self.groups.get(y) {
index c5a0ebfdfc40b736bfc4466d11102da74b0f79b9..48a974b48651e959fa405038dd18a7994c678ae5 100644 (file)
@@ -28,7 +28,7 @@ use crate::{
     data::{ByteString, Datum, EncodedString, WithEncoding},
     format::{DATETIME40_0, F8_2, F40, Format, TIME40_0, Type, UncheckedFormat},
     output::pivot::{
-        DisplayMarker, Footnote, FootnoteMarkerType,
+        Footnote, FootnoteMarkerType,
         look::{CellStyle, FontStyle},
     },
     settings::{Settings, Show},
@@ -392,8 +392,8 @@ impl Value {
     /// Returns an object that will format this value, including subscripts and
     /// superscripts and footnotes.  `options` controls whether variable and
     /// value labels are included.
-    pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
-        let display = self.inner.display(options.into_value_options());
+    pub fn display(&self, options: impl Into<ValueOptions>) -> DisplayValue<'_> {
+        let display = self.inner.display(options);
         match &self.styling {
             Some(styling) => display.with_styling(styling),
             None => display,
@@ -453,16 +453,21 @@ impl<'a> DisplayValue<'a> {
         !self.subscripts.is_empty()
     }
 
-    /// Returns the footnotes to be displayed, as an iterator of
-    /// [DisplayMarker].
-    pub fn footnotes(&self) -> impl Iterator<Item = DisplayMarker<'_>> + Clone {
+    /// Returns the footnotes to be displayed, as an iterator.
+    ///
+    /// The iterator can have fewer elements than there are footnotes, because
+    /// footnotes can be hidden.
+    pub fn footnotes(&self) -> impl Iterator<Item = impl Display> + Clone {
         self.footnotes
             .iter()
             .filter(|f| f.show)
             .map(|f| f.display_marker(self.options))
     }
 
-    /// Returns true if the value to be displayed includes footnotes.
+    /// Returns true if there are footnotes to be displayed.
+    ///
+    /// Because footnotes can be hidden, this method can return false for values
+    /// with footnotes.
     pub fn has_footnotes(&self) -> bool {
         self.footnotes().next().is_some()
     }
@@ -1118,7 +1123,7 @@ impl ValueInner {
 
     /// Returns an object that will format this value.  Settings on `options`
     /// control whether variable and value labels are included.
-    pub fn display(&self, options: impl IntoValueOptions) -> DisplayValue<'_> {
+    pub fn display(&self, options: impl Into<ValueOptions>) -> DisplayValue<'_> {
         fn interpret_show(
             global_show: impl Fn() -> Show,
             table_show: Option<Show>,
@@ -1132,7 +1137,7 @@ impl ValueInner {
             }
         }
 
-        let options = options.into_value_options();
+        let options = options.into();
         let (show_value, show_label) = if let Some(value_label) = self.value_label() {
             interpret_show(
                 || Settings::global().show_values,
@@ -1225,29 +1230,14 @@ impl Default for ValueOptions {
     }
 }
 
-/// Obtains [ValueOptions] in various ways.
-pub trait IntoValueOptions {
-    /// Returns [ValueOptions] for this type.
-    fn into_value_options(self) -> ValueOptions;
-}
-
-/// Default [ValueOptions].
-impl IntoValueOptions for () {
-    fn into_value_options(self) -> ValueOptions {
+impl From<()> for ValueOptions {
+    fn from(_: ()) -> Self {
         ValueOptions::default()
     }
 }
 
-/// Copies [ValueOptions] by reference.
-impl IntoValueOptions for &ValueOptions {
-    fn into_value_options(self) -> ValueOptions {
-        *self
-    }
-}
-
-/// Copies [ValueOptions] by value.
-impl IntoValueOptions for ValueOptions {
-    fn into_value_options(self) -> ValueOptions {
-        self
+impl From<&ValueOptions> for ValueOptions {
+    fn from(value: &ValueOptions) -> Self {
+        value.clone()
     }
 }
index 4bf2c26f839ceb83feadd3f13af219c89e820954..5d63ba8a7c6a007f2e4a22f3270a4527adfe55ce 100644 (file)
@@ -339,7 +339,7 @@ impl Table {
         headers: CellPos,
         areas: EnumMap<Area, AreaStyle>,
         borders: EnumMap<Border, BorderStyle>,
-        value_options: ValueOptions,
+        value_options: impl Into<ValueOptions>,
     ) -> Self {
         Self {
             n,
@@ -351,7 +351,7 @@ impl Table {
                 Axis2::X => Array::default((n.x + 1, n.y)),
                 Axis2::Y => Array::default((n.x, n.y + 1)),
             },
-            value_options,
+            value_options: value_options.into(),
         }
     }