work
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 16 Oct 2025 14:18:54 +0000 (07:18 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 16 Oct 2025 14:18:54 +0000 (07:18 -0700)
rust/doc/src/SUMMARY.md
rust/doc/src/invoking/pspp-identify.md [new file with mode: 0644]
rust/doc/src/spv/structure.md
rust/pspp/src/file.rs
rust/pspp/src/identify.rs [new file with mode: 0644]
rust/pspp/src/main.rs
rust/pspp/src/output/spv.rs

index 456092cf62ae3e58d31bfe8730207aff26af7119..1810af5d4e63a4753e782d7ce1ddd4224a6fd259 100644 (file)
@@ -9,6 +9,7 @@
   - [Inspecting Portable Files](invoking/pspp-show-por.md)
   - [Inspecting SPSS/PC+ Files](invoking/pspp-show-pc.md)
   - [Inspecting SPSS Viewer Files](invoking/pspp-show-spv.md)
+  - [Identifying Files](invoking/pspp-identify.md)
   - [Decrypting Files](invoking/pspp-decrypt.md)
   - [Output Driver Configuration](invoking/output.md)
 
diff --git a/rust/doc/src/invoking/pspp-identify.md b/rust/doc/src/invoking/pspp-identify.md
new file mode 100644 (file)
index 0000000..4d4546a
--- /dev/null
@@ -0,0 +1,26 @@
+# Identifying Files
+
+The `pspp identify` command identifies file types.  The syntax is:
+
+```
+pspp identify <FILE>
+```
+
+which reads `<FILE>` and identifies it based on its contents.  The
+command prints one of the following on the terminal:
+
+* `sav`, for an SPSS system file, `sav (encrypted)` if the file is
+  encrypted.
+
+* `por`, for an SPSS portable file.
+
+* `pc+`, for an SPSS/PC+ data file.
+
+* `spv`, for an SPSS Viewer file, or `spv (encrypted)` if the file is
+  encrypted.
+
+* `sps`, for an SPSS syntax file, `sps (encrypted)` if the file is
+  encrypted, or `sps (low confidence)` if the file could be a syntax
+  file or another kind of text file.
+
+* `unknown`, for other kinds of files.
index 1241c61ce770e52949dd69cc72397ced4cc9fc4c..4e34d7344fddc8904f7209f55afea5d4b2389272 100644 (file)
@@ -40,7 +40,7 @@ element:
 
 ```
 container
-   :visibility=(visible | hidden)
+   :visibility=(visible | hidden)?
    :page-break-before=(always)?
    :text-align=(left | center)?
    :width=dimension
@@ -305,7 +305,7 @@ anyway.  The user cannot edit it.
 
 ```
 container
-   :visibility=(visible | hidden)
+   :visibility=(visible | hidden)?
    :page-break-before=(always | auto | avoid | left | right | inherit)?
    :text-align=(left | center)?
    :width=dimension
@@ -319,7 +319,8 @@ This element has the following attributes.
 
 * `visibility`  
   Whether the container's content is displayed.  "Notes" tables are
-  often hidden; other data is usually visible.
+  often hidden; other data is usually visible.  The default is
+  `visible`.
 
 * `page-break-before`  
   Whether to start the element at the beginning of a new page.  This
index bc9bb85ec48db0185a8b6ea770947df1e9d12dfe..1baa00e7a9451321646f156260e1a5c4524485f1 100644 (file)
@@ -170,6 +170,21 @@ impl FileType {
 
         Ok(None)
     }
+
+    /// Returns a string for the typical extension associated with this kind of
+    /// file, without the leading `.`.
+    ///
+    /// Returns `pc+` for [FileType::Pc] files, even though that is not typical,
+    /// since these files are so unusual.
+    pub fn as_extension(&self) -> &'static str {
+        match self {
+            FileType::System { .. } => "sav",
+            FileType::Portable => "por",
+            FileType::Pc => "pc+",
+            FileType::Viewer { .. } => "spv",
+            FileType::Syntax { .. } => "sps",
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/rust/pspp/src/identify.rs b/rust/pspp/src/identify.rs
new file mode 100644 (file)
index 0000000..917e863
--- /dev/null
@@ -0,0 +1,46 @@
+// PSPP - a program for statistical analysis.
+// Copyright (C) 2025 Free Software Foundation, Inc.
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, either version 3 of the License, or (at your option) any later
+// version.
+//
+// This program 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 General Public License for more
+// details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program.  If not, see <http://www.gnu.org/licenses/>.
+
+use anyhow::Result;
+use clap::Args;
+use pspp::file::FileType;
+use std::path::PathBuf;
+
+/// Identify the type of a file.
+#[derive(Args, Clone, Debug)]
+pub struct Identify {
+    /// File to identify.
+    file: PathBuf,
+}
+
+impl Identify {
+    pub fn run(self) -> Result<()> {
+        match FileType::from_file(&self.file)? {
+            None => println!("unknown"),
+            Some(file_type) => {
+                print!("{}", file_type.as_extension());
+                if file_type.is_encrypted() {
+                    print!(" (encrypted)");
+                }
+                if !file_type.is_confident() {
+                    print!(" (low confidence)");
+                }
+                println!();
+            }
+        }
+        Ok(())
+    }
+}
index de9293200f35e212196d618330406c62d34fcc13..df4d3715b360bc9b74e9ad8611e0489937c0fb2c 100644 (file)
@@ -1,18 +1,18 @@
-/* PSPP - a program for statistical analysis.
- * Copyright (C) 2023 Free Software Foundation, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+// PSPP - a program for statistical analysis.
+// Copyright (C) 2025 Free Software Foundation, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 use anyhow::Result;
 use clap::{Parser, Subcommand};
@@ -20,12 +20,13 @@ use encoding_rs::Encoding;
 use thiserror::Error as ThisError;
 
 use crate::{
-    convert::Convert, decrypt::Decrypt, show::Show, show_pc::ShowPc, show_por::ShowPor,
-    show_spv::ShowSpv,
+    convert::Convert, decrypt::Decrypt, identify::Identify, show::Show, show_pc::ShowPc,
+    show_por::ShowPor, show_spv::ShowSpv,
 };
 
 mod convert;
 mod decrypt;
+mod identify;
 mod show;
 mod show_pc;
 mod show_por;
@@ -43,6 +44,7 @@ struct Cli {
 enum Command {
     Convert(Convert),
     Decrypt(Decrypt),
+    Identify(Identify),
     Show(Show),
     ShowPor(ShowPor),
     ShowPc(ShowPc),
@@ -54,6 +56,7 @@ impl Command {
         match self {
             Command::Convert(convert) => convert.run(),
             Command::Decrypt(decrypt) => decrypt.run(),
+            Command::Identify(identify) => identify.run(),
             Command::Show(show) => show.run(),
             Command::ShowPor(show_por) => show_por.run(),
             Command::ShowPc(show_pc) => show_pc.run(),
index d1cdc1a695d61622f8b37e22b2c9812eea177621..aab671e2b373c27b5430edc4541274a3361dcfda 100644 (file)
@@ -183,6 +183,10 @@ impl Heading {
                         .into_item()
                         .with_command_name(container_text.command_name)
                         .with_spv_info(SpvInfo::new(structure_member)),
+                        ContainerContent::Model => new_error_item("models not yet implemented")
+                            .with_spv_info(SpvInfo::new(structure_member).with_error()),
+                        ContainerContent::Object => new_error_item("objects not yet implemented")
+                            .with_spv_info(SpvInfo::new(structure_member).with_error()),
                         ContainerContent::Tree => new_error_item("trees not yet implemented")
                             .with_spv_info(SpvInfo::new(structure_member).with_error()),
                     };
@@ -219,14 +223,14 @@ enum HeadingContent {
 #[derive(Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 struct Label {
-    #[serde(rename = "$text")]
+    #[serde(default, rename = "$text")]
     text: String,
 }
 
 #[derive(Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 struct Container {
-    #[serde(rename = "@visibility")]
+    #[serde(default, rename = "@visibility")]
     visibility: Visibility,
     #[serde(rename = "@page-break-before")]
     #[serde(default)]
@@ -253,9 +257,10 @@ enum PageBreakBefore {
     Inherit,
 }
 
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Debug, Default)]
 #[serde(rename_all = "camelCase")]
 enum Visibility {
+    #[default]
     Visible,
     Hidden,
 }
@@ -274,10 +279,9 @@ enum ContainerContent {
     Table(Table),
     Text(ContainerText),
     Graph(Graph),
-    /*
-    Model(Model),
-    Object(Object),
-    Image(Image),*/
+    Model,
+    Object,
+    //Image(Image),
     Tree,
 }