Skip to main content

vortex_tui/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Vortex TUI library for interactively browsing and inspecting Vortex files.
5//!
6//! This crate provides both a CLI tool (`vx`) and a library API for working with Vortex files.
7//! Users can bring their own `VortexSession` to enable custom encodings and extensions.
8//!
9//! # Example
10//!
11//! ```ignore
12//! use vortex::session::VortexSession;
13//! use vortex::io::session::RuntimeSessionExt;
14//! use vortex_tui::browse;
15//!
16//! #[tokio::main]
17//! async fn main() -> anyhow::Result<()> {
18//!     let session = VortexSession::default().with_tokio();
19//!     browse::exec_tui(&session, "my_file.vortex").await?;
20//!     Ok(())
21//! }
22//! ```
23
24#![deny(clippy::missing_errors_doc)]
25#![deny(clippy::missing_panics_doc)]
26#![deny(clippy::missing_safety_doc)]
27#![deny(missing_docs)]
28
29#[cfg_attr(
30    all(not(feature = "native"), not(target_arch = "wasm32")),
31    allow(dead_code, unused_imports)
32)]
33pub mod browse;
34pub mod segment_tree;
35
36#[cfg(feature = "native")]
37pub mod convert;
38#[cfg(feature = "native")]
39pub mod datafusion_helper;
40#[cfg(feature = "native")]
41pub mod inspect;
42#[cfg(feature = "native")]
43pub mod query;
44#[cfg(feature = "native")]
45pub mod segments;
46#[cfg(feature = "native")]
47pub mod tree;
48
49#[cfg(target_arch = "wasm32")]
50pub mod wasm;
51
52#[cfg(feature = "native")]
53mod native_cli {
54    use std::ffi::OsString;
55    use std::path::PathBuf;
56
57    use clap::CommandFactory;
58    use clap::Parser;
59    use vortex::error::VortexExpect;
60    use vortex::session::VortexSession;
61
62    #[derive(clap::Parser)]
63    #[command(version)]
64    struct Cli {
65        #[clap(subcommand)]
66        command: Commands,
67    }
68
69    #[derive(Debug, clap::Subcommand)]
70    enum Commands {
71        /// Print tree views of a Vortex file (layout tree or array tree)
72        Tree(super::tree::TreeArgs),
73        /// Convert a Parquet file to a Vortex file. Chunking occurs on Parquet RowGroup boundaries.
74        Convert(#[command(flatten)] super::convert::ConvertArgs),
75        /// Interactively browse the Vortex file.
76        Browse { file: PathBuf },
77        /// Inspect Vortex file footer and metadata
78        Inspect(super::inspect::InspectArgs),
79        /// Execute a SQL query against a Vortex file using DataFusion
80        Query(super::query::QueryArgs),
81        /// Display segment information for a Vortex file
82        Segments(super::segments::SegmentsArgs),
83    }
84
85    impl Commands {
86        fn file_path(&self) -> &PathBuf {
87            match self {
88                Commands::Tree(args) => match &args.mode {
89                    super::tree::TreeMode::Array { file, .. } => file,
90                    super::tree::TreeMode::Layout { file, .. } => file,
91                },
92                Commands::Browse { file } => file,
93                Commands::Convert(flags) => &flags.file,
94                Commands::Inspect(args) => &args.file,
95                Commands::Query(args) => &args.file,
96                Commands::Segments(args) => &args.file,
97            }
98        }
99    }
100
101    /// Main entrypoint for `vx` that launches a [`VortexSession`].
102    ///
103    /// Parses arguments from [`std::env::args_os`]. See [`launch_from`] to supply explicit arguments.
104    ///
105    /// # Errors
106    ///
107    /// Raises any errors from subcommands.
108    pub async fn launch(session: &VortexSession) -> anyhow::Result<()> {
109        launch_from(session, std::env::args_os()).await
110    }
111
112    /// Launch `vx` with explicit command-line arguments.
113    ///
114    /// This is useful when embedding the TUI inside another process (e.g. Python) where
115    /// [`std::env::args`] may not reflect the intended arguments.
116    ///
117    /// # Errors
118    ///
119    /// Raises any errors from subcommands.
120    pub async fn launch_from(
121        session: &VortexSession,
122        args: impl IntoIterator<Item = impl Into<OsString> + Clone>,
123    ) -> anyhow::Result<()> {
124        let _ = env_logger::try_init();
125
126        // Use `try_parse_from` rather than `parse_from` so we never call
127        // `std::process::exit` from library code — that segfaults when
128        // `launch_from` runs inside a Python-loaded dylib.
129        // See https://github.com/vortex-data/vortex/issues/7910.
130        let cli = Cli::try_parse_from(args)?;
131
132        let path = cli.command.file_path();
133        if !std::fs::exists(path)? {
134            return Err(Cli::command()
135                .error(
136                    clap::error::ErrorKind::Io,
137                    format!(
138                        "File '{}' does not exist.",
139                        path.to_str().vortex_expect("file path")
140                    ),
141                )
142                .into());
143        }
144
145        match cli.command {
146            Commands::Tree(args) => super::tree::exec_tree(session, args).await?,
147            Commands::Convert(flags) => super::convert::exec_convert(session, flags).await?,
148            Commands::Browse { file } => super::browse::exec_tui(session, file).await?,
149            Commands::Inspect(args) => super::inspect::exec_inspect(session, args).await?,
150            Commands::Query(args) => super::query::exec_query(session, args).await?,
151            Commands::Segments(args) => super::segments::exec_segments(session, args).await?,
152        };
153
154        Ok(())
155    }
156
157    #[cfg(test)]
158    mod tests {
159        use vortex::VortexSessionDefault;
160        use vortex::session::VortexSession;
161
162        /// Regression test for https://github.com/vortex-data/vortex/issues/7910:
163        /// `vx --help` must surface as a `clap::Error` rather than calling
164        /// `std::process::exit`, which segfaults when invoked via the Python
165        /// binding.
166        #[tokio::test]
167        async fn help_flag_surfaces_clap_error() {
168            let err = super::launch_from(&VortexSession::default(), ["vx", "--help"])
169                .await
170                .expect_err("--help must surface as a clap::Error");
171            assert_eq!(
172                err.downcast_ref::<clap::Error>().map(clap::Error::kind),
173                Some(clap::error::ErrorKind::DisplayHelp),
174            );
175        }
176    }
177}
178
179#[cfg(feature = "native")]
180pub use native_cli::launch;
181#[cfg(feature = "native")]
182pub use native_cli::launch_from;