From: Ben Pfaff Date: Sun, 14 Dec 2025 21:32:58 +0000 (-0800) Subject: work X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=05e2a190dbd5e285a45fe5da9ad6d00fb05a0bc0;p=pspp work --- diff --git a/rust/pspp/src/format.rs b/rust/pspp/src/format.rs index 8a0afb3898..bc61eacc04 100644 --- a/rust/pspp/src/format.rs +++ b/rust/pspp/src/format.rs @@ -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, diff --git a/rust/pspp/src/output/drivers/text.rs b/rust/pspp/src/output/drivers/text.rs index 333a902908..5827fef5f8 100644 --- a/rust/pspp/src/output/drivers/text.rs +++ b/rust/pspp/src/output/drivers/text.rs @@ -332,7 +332,9 @@ static UNICODE_BOX: LazyLock = 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, } diff --git a/rust/pspp/src/output/pivot.rs b/rust/pspp/src/output/pivot.rs index f72874ed33..5eb53af119 100644 --- a/rust/pspp/src/output/pivot.rs +++ b/rust/pspp/src/output/pivot.rs @@ -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> { 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 { 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 { (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, + /// 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, - - /// 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) -> 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, 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) { 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) -> Self { self.push(child); self } + /// Returns this group with the given `children` appended to its categories. pub fn with_multiple(mut self, children: impl IntoIterator) -> Self where C: Into, @@ -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> { + /// 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> { 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>); impl Footnotes { + /// Constructs a new, empty collection of footnotes. pub fn new() -> Self { Self::default() } - pub fn push(&mut self, footnote: Footnote) -> Arc { - 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 { + 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> { self.0.get(index) } @@ -865,13 +941,17 @@ impl FromIterator for Footnotes { } } +/// A leaf category within a [Group] and ultimately within a [Dimension]. #[derive(Clone, Debug)] -pub struct Leaf(Box); +pub struct Leaf(pub Box); 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> { + fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option> { 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 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(x: T, y: T) -> EnumMap { 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 for Axis2 { pub struct Coord2(pub EnumMap); 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) -> 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 for Coord2 { } } +/// A 2-dimensional rectangle. #[derive(Clone, Debug, Default)] -pub struct Rect2(pub EnumMap>); +pub struct Rect2( + /// The range along each axis. + pub EnumMap>, +); impl Rect2 { + /// Constructs a new `Rect2` that covers the given X and Y ranges. pub fn new(x_range: Range, y_range: Range) -> 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), b_range: Range) -> 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) -> Self where F: FnMut(Axis2) -> Range, { 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 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) -> Self { + /// Returns this style with the given `look`. + pub fn with_look(self, look: Arc) -> Self { Self { look, ..self } } - fn with_show_values(self, show_values: Option) -> Self { + + /// Returns this style with the given `show_values`. + pub fn with_show_values(self, show_values: Option) -> Self { Self { show_values, ..self } } - fn with_show_variables(self, show_variables: Option) -> Self { + + /// Returns this style with the given `show_variables`. + pub fn with_show_variables(self, show_variables: Option) -> 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) -> 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 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, + /// Metadata. pub metadata: PivotTableMetadata, + + /// Footnotes. pub footnotes: Footnotes, + + /// Dimensions. dimensions: Vec, + + /// Axes. axes: EnumMap, + + /// Data. cells: HashMap, } @@ -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(self, dimensions: I) -> usize where I: ExactSizeIterator; @@ -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(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, + + /// The footnote marker. + /// + /// This is usually `None`, in which case [FootnoteMarkerType] determines + /// the default marker. pub marker: Option>, + + /// Whether to show the footnote. pub show: bool, } impl Footnote { + /// Constructs a new footnote. pub fn new(content: impl Into) -> 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) -> Self { Self { marker: marker.map(Box::new), ..self } } + + /// Returns the footnote with the given marker. pub fn with_some_marker(self, marker: impl Into) -> 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) -> 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) -> 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: 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), + /// A value. + Leaf( + /// The value. + Value, + ), + /// A nested group of entries. + Group( + /// The entries. + Vec, + ), } impl MetadataValue { + /// Construct a new "leaf" metadata value. pub fn new_leaf(value: impl Into) -> Self { Self::Leaf(value.into()) } diff --git a/rust/pspp/src/output/pivot/look.rs b/rust/pspp/src/output/pivot/look.rs index 7157377d76..3528a7a4da 100644 --- a/rust/pspp/src/output/pivot/look.rs +++ b/rust/pspp/src/output/pivot/look.rs @@ -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 { diff --git a/rust/pspp/src/output/pivot/look_xml.rs b/rust/pspp/src/output/pivot/look_xml.rs index 429b59538c..6215acd558 100644 --- a/rust/pspp/src/output/pivot/look_xml.rs +++ b/rust/pspp/src/output/pivot/look_xml.rs @@ -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() } diff --git a/rust/pspp/src/output/pivot/output.rs b/rust/pspp/src/output/pivot/output.rs index 25bf952301..6b36ececc1 100644 --- a/rust/pspp/src/output/pivot/output.rs +++ b/rust/pspp/src/output/pivot/output.rs @@ -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 { 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
{ 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
{ 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]) -> Option
{ 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
, + /// Layers table, if any. pub layers: Option
, + /// Table body. pub body: Table, + /// Table caption, if any. pub caption: Option
, + /// Footnotes, if any. pub footnotes: Option
, } 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) { debug_assert!(height > self.groups.len()); if let Some(group) = self.groups.get(y) { diff --git a/rust/pspp/src/output/pivot/value.rs b/rust/pspp/src/output/pivot/value.rs index c5a0ebfdfc..48a974b486 100644 --- a/rust/pspp/src/output/pivot/value.rs +++ b/rust/pspp/src/output/pivot/value.rs @@ -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) -> 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> + 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 + 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) -> DisplayValue<'_> { fn interpret_show( global_show: impl Fn() -> Show, table_show: Option, @@ -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() } } diff --git a/rust/pspp/src/output/table.rs b/rust/pspp/src/output/table.rs index 4bf2c26f83..5d63ba8a7c 100644 --- a/rust/pspp/src/output/table.rs +++ b/rust/pspp/src/output/table.rs @@ -339,7 +339,7 @@ impl Table { headers: CellPos, areas: EnumMap, borders: EnumMap, - value_options: ValueOptions, + value_options: impl Into, ) -> 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(), } }