nu-protocol 0.114.0

Nushell's internal protocols, including its abstract syntax tree
Documentation
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::{Record, Span, Value};

pub const TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY: &str = "table_width_priority_columns";

/// Metadata that is valid for the whole [`PipelineData`](crate::PipelineData)
///
/// ## Custom Metadata
///
/// The `custom` field allows commands and plugins to attach arbitrary metadata to pipeline data.
/// To avoid key collisions, it is recommended to use namespaced keys with an underscore separator:
///
/// - `"http_response"` - HTTP response metadata (status, headers, etc.)
/// - `"polars_schema"` - DataFrame schema information
/// - `"custom_plugin_field"` - Plugin-specific metadata
///
/// This convention helps ensure different commands and plugins don't overwrite each other's metadata.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct PipelineMetadata {
    pub data_source: DataSource,
    pub path_columns: Vec<String>,
    pub content_type: Option<String>,
    #[serde(default)]
    pub custom: Record,
}

impl PipelineMetadata {
    pub fn with_data_source(self, data_source: DataSource) -> Self {
        Self {
            data_source,
            ..self
        }
    }

    pub fn with_path_columns(self, path_columns: Vec<String>) -> Self {
        Self {
            path_columns,
            ..self
        }
    }

    pub fn with_content_type(self, content_type: Option<String>) -> Self {
        Self {
            content_type,
            ..self
        }
    }

    /// Set table width-priority columns in custom metadata.
    ///
    /// This helper accepts plain strings to avoid manually constructing `Value::list`.
    pub fn set_table_width_priority_columns<I, S>(&mut self, span: Span, columns: I)
    where
        I: IntoIterator<Item = S>,
        S: AsRef<str>,
    {
        let columns: Vec<Value> = columns
            .into_iter()
            .map(|column| Value::string(column.as_ref(), span))
            .collect();

        // Replace an existing entry if present.
        self.custom
            .retain(|key, _| key != TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY);

        if !columns.is_empty() {
            self.custom.push(
                TABLE_WIDTH_PRIORITY_COLUMNS_METADATA_KEY.to_string(),
                Value::list(columns, span),
            );
        }
    }

    /// Builder-style variant of [`PipelineMetadata::set_table_width_priority_columns`].
    pub fn with_table_width_priority_columns<I, S>(mut self, span: Span, columns: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: AsRef<str>,
    {
        self.set_table_width_priority_columns(span, columns);
        self
    }

    /// Transform metadata for the `collect` operation.
    ///
    /// After collecting a stream into a value, `FilePath` data sources are no longer meaningful
    /// and should be converted to `None`. If all metadata fields become empty after this
    /// transformation, returns `None` to avoid carrying around empty metadata.
    pub fn for_collect(self) -> Option<Self> {
        let Self {
            data_source,
            path_columns,
            content_type,
            custom,
        } = self;

        // Transform FilePath to None after collect
        let data_source = match data_source {
            DataSource::FilePath(_) => DataSource::None,
            other => other,
        };

        // Return None if completely empty
        if matches!(data_source, DataSource::None)
            && path_columns.is_empty()
            && content_type.is_none()
            && custom.is_empty()
        {
            None
        } else {
            Some(Self {
                data_source,
                path_columns,
                content_type,
                custom,
            })
        }
    }
}

/// Describes where the particular [`PipelineMetadata`] originates.
///
/// This can either be a particular family of commands or the opened file to protect against
/// overwrite-attempts properly.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub enum DataSource {
    HtmlThemes,
    FilePath(PathBuf),
    #[default]
    None,
}