work
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 9 Dec 2025 20:10:10 +0000 (12:10 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 9 Dec 2025 20:10:10 +0000 (12:10 -0800)
rust/pspp/src/output/drivers/text.rs
rust/pspp/src/output/pivot.rs

index cc6cad04326e95508874607a5fe4d3ca2a6bf2d3..423664dd029871abdf3f03fe32626a64f8412221 100644 (file)
@@ -392,7 +392,8 @@ impl TextRenderer {
                 writeln!(writer)?;
             }
             match &item.details {
-                Details::Chart | Details::Image(_) => todo!(),
+                Details::Chart => writeln!(writer, "Omitting chart from text output")?,
+                Details::Image(_) => writeln!(writer, "Omitting image from text output")?,
                 Details::Heading(_) => unreachable!(),
                 Details::Message(_diagnostic) => todo!(),
                 Details::PageBreak => (),
index 448e95f92c20ae7d71bc9763828bac9a0dd54b07..4bbcf2dab9e7f1de26db8bbd05582a9288a269fb 100644 (file)
@@ -44,7 +44,7 @@
 
 use std::{
     collections::HashMap,
-    fmt::{Debug, Display},
+    fmt::{Debug, Display, Write},
     io::Read,
     iter::{FusedIterator, once, repeat, repeat_n},
     ops::{Index, IndexMut, Not, Range, RangeInclusive},
@@ -2773,9 +2773,7 @@ impl Display for DisplayValue<'_> {
 
             ValueInner::Markup(markup) => write!(f, "{markup}"),
 
-            ValueInner::Text(TextValue {
-                localized: local, ..
-            }) => f.write_str(local),
+            ValueInner::Text(text_value) => write!(f, "{text_value}"),
 
             ValueInner::Template(template_value) => template_value.display(self, f),
 
@@ -2829,14 +2827,32 @@ impl Debug for Value {
     }
 }
 
+/// A numeric value and how to display it.
 #[derive(Clone, Debug, PartialEq)]
 pub struct NumberValue {
     /// The numerical value, or `None` if it is a missing value.
     pub value: Option<f64>,
+
+    /// The display format (usually [F] or [Pct]).
+    ///
+    /// [F]: crate::format::Type::F
+    /// [Pct]: crate::format::Type::Pct
     pub format: Format,
+
+    /// Whether to show `value` or `value_label` or both.
+    ///
+    /// If this is unset, then a higher-level default is used.
     pub show: Option<Show>,
+
+    /// If true, then numbers smaller than a threshold will be displayed in
+    /// scientific notation.  Otherwise, all numbers will be displayed with
+    /// `format`.
     pub honor_small: bool,
+
+    /// The name of the variable that `value` came from, if any.
     pub variable: Option<String>,
+
+    /// The value label associated with `value`, if any.
     pub value_label: Option<String>,
 }
 
@@ -2920,6 +2936,7 @@ pub struct BareNumberValue<'a>(
     #[serde(serialize_with = "NumberValue::serialize_bare")] pub &'a NumberValue,
 );
 
+/// A string value and how to display it.
 #[derive(Clone, Debug, Serialize, PartialEq)]
 pub struct StringValue {
     /// The string value.
@@ -2931,27 +2948,56 @@ pub struct StringValue {
     /// True if `s` is hex digits.
     pub hex: bool,
 
+    /// Whether to show `s` or `value_label` or both.
+    ///
+    /// If this is unset, then a higher-level default is used.
     pub show: Option<Show>,
 
+    /// The name of the variable that `s` came from, if any.
     pub var_name: Option<String>,
+
+    /// The value label associated with `s`, if any.
     pub value_label: Option<String>,
 }
 
 #[derive(Clone, Debug, Serialize, PartialEq)]
 pub struct VariableValue {
+    /// Whether to show `var_name` or `variable_label` or both.
+    ///
+    /// If this is unset, then a higher-level default is used.
     pub show: Option<Show>,
     pub var_name: String,
     pub variable_label: Option<String>,
 }
 
+/// A text string.
+///
+/// Whereas a [StringValue] is usually related to data, a `TextValue` is used
+/// for other text within a table, such as a title, a column or row heading, or
+/// a footnote.
 #[derive(Clone, Debug, PartialEq)]
 pub struct TextValue {
+    /// Whether the text came from the user.
+    ///
+    /// PSPP can localize text that it writes itself, but not text provided by
+    /// the user.
     pub user_provided: bool,
+
     /// Localized.
+    ///
+    /// This is the main output string.
     pub localized: String,
-    /// English.
+
+    /// English version of the string.
+    ///
+    /// Only for strings that are not user-provided, and only if it is different
+    /// from `localized`.
     pub c: Option<String>,
+
     /// Identifier.
+    ///
+    /// Only for strings that are not user-provided, and only if it is different
+    /// from `localized`.
     pub id: Option<String>,
 }
 
@@ -2980,6 +3026,12 @@ impl Serialize for TextValue {
     }
 }
 
+impl Display for TextValue {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.localized)
+    }
+}
+
 impl TextValue {
     pub fn localized(&self) -> &str {
         self.localized.as_str()
@@ -3020,19 +3072,22 @@ impl TemplateValue {
         while let Some(c) = iter.next() {
             match c {
                 '\\' => {
-                    let c = iter.next().unwrap_or('\\') as char;
-                    let c = if c == 'n' { '\n' } else { c };
-                    write!(f, "{c}")?;
+                    let c = match iter.next() {
+                        None => '\\',
+                        Some('n') => '\n',
+                        Some(c) => c,
+                    };
+                    f.write_char(c)?;
                 }
                 '^' => {
                     let (index, rest) = Self::consume_int(iter.as_str());
-                    iter = rest.chars();
-                    let Some(arg) = self.args.get(index.wrapping_sub(1)) else {
-                        continue;
-                    };
-                    if let Some(arg) = arg.first() {
-                        write!(f, "{}", arg.display(display.options))?;
+                    if let Some(index) = index.checked_sub(1)
+                        && let Some(arg) = self.args.get(index)
+                        && let Some(arg) = arg.first()
+                    {
+                        arg.display(display.options).fmt(f)?;
                     }
+                    iter = rest.chars();
                 }
                 '[' => {
                     let (a, rest) = extract_inner_template(iter.as_str());
@@ -3041,28 +3096,24 @@ impl TemplateValue {
                     let (index, rest) = Self::consume_int(rest);
                     iter = rest.chars();
 
-                    dbg!((a, b, index));
-                    let Some(mut args) = self
-                        .args
-                        .get(index.wrapping_sub(1))
-                        .map(|vec| vec.as_slice())
-                    else {
-                        continue;
-                    };
-                    let (mut template, mut escape) =
-                        if !a.is_empty() { (a, '%') } else { (b, '^') };
-                    while !args.is_empty() {
-                        let n_consumed = self.inner_template(display, f, template, escape, args)?;
-                        if n_consumed == 0 {
-                            break;
+                    if let Some(index) = index.checked_sub(1)
+                        && let Some(args) = self.args.get(index)
+                    {
+                        let mut args = args.as_slice();
+                        let (mut template, mut escape) =
+                            if !a.is_empty() { (a, '%') } else { (b, '^') };
+                        while !args.is_empty()
+                            && let n_consumed =
+                                self.inner_template(display, f, template, escape, args)?
+                            && n_consumed > 0
+                        {
+                            args = &args[n_consumed..];
+                            template = b;
+                            escape = '^';
                         }
-                        args = &args[n_consumed..];
-
-                        template = b;
-                        escape = '^';
                     }
                 }
-                c => write!(f, "{c}")?,
+                c => f.write_char(c)?,
             }
         }
         Ok(())
@@ -3088,11 +3139,12 @@ impl TemplateValue {
                 c if c == escape => {
                     let (index, rest) = Self::consume_int(iter.as_str());
                     iter = rest.chars();
-                    let Some(arg) = args.get(index.wrapping_sub(1)) else {
-                        continue;
-                    };
-                    args_consumed = args_consumed.max(index);
-                    write!(f, "{}", arg.display(display.options))?;
+                    if let Some(index) = index.checked_sub(1)
+                        && let Some(arg) = args.get(index)
+                    {
+                        args_consumed = args_consumed.max(index);
+                        write!(f, "{}", arg.display(display.options))?;
+                    }
                 }
                 c => write!(f, "{c}")?,
             }