Skip to main content

xs/nu/commands/
cat_command.rs

1use nu_engine::CallExt;
2use nu_protocol::engine::{Call, Command, EngineState, Stack};
3use nu_protocol::shell_error::generic::GenericError;
4use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Type};
5
6use crate::store::{ReadOptions, Store};
7
8#[derive(Clone)]
9pub struct CatCommand {
10    store: Store,
11}
12
13impl CatCommand {
14    pub fn new(store: Store) -> Self {
15        Self { store }
16    }
17}
18
19impl Command for CatCommand {
20    fn name(&self) -> &str {
21        ".cat"
22    }
23
24    fn signature(&self) -> Signature {
25        Signature::build(".cat")
26            .input_output_types(vec![(Type::Nothing, Type::Any)])
27            .named(
28                "limit",
29                SyntaxShape::Int,
30                "limit the number of frames to retrieve",
31                None,
32            )
33            .named(
34                "after",
35                SyntaxShape::String,
36                "start after a specific frame ID (exclusive)",
37                Some('a'),
38            )
39            .named(
40                "from",
41                SyntaxShape::String,
42                "start from a specific frame ID (inclusive)",
43                None,
44            )
45            .named(
46                "last",
47                SyntaxShape::Int,
48                "return the N most recent frames",
49                None,
50            )
51            .named(
52                "topic",
53                SyntaxShape::OneOf(vec![
54                    SyntaxShape::String,
55                    SyntaxShape::List(Box::new(SyntaxShape::String)),
56                ]),
57                "filter by topic pattern(s): string (commas allowed) or list",
58                Some('T'),
59            )
60            .switch(
61                "with-timestamp",
62                "include timestamp extracted from frame ID",
63                None,
64            )
65            .category(Category::Experimental)
66    }
67
68    fn description(&self) -> &str {
69        "Reads the event stream and returns frames"
70    }
71
72    fn run(
73        &self,
74        engine_state: &EngineState,
75        stack: &mut Stack,
76        call: &Call,
77        _input: PipelineData,
78    ) -> Result<PipelineData, ShellError> {
79        let limit: Option<usize> = call.get_flag(engine_state, stack, "limit")?;
80        let last: Option<usize> = call.get_flag(engine_state, stack, "last")?;
81        let after: Option<String> = call.get_flag(engine_state, stack, "after")?;
82        let from: Option<String> = call.get_flag(engine_state, stack, "from")?;
83        let topic: Option<nu_protocol::Value> = call.get_flag(engine_state, stack, "topic")?;
84        let topic: Option<String> = topic
85            .map(crate::nu::util::topic_value_to_string)
86            .transpose()?;
87        let with_timestamp = call.has_flag(engine_state, stack, "with-timestamp")?;
88
89        // Helper to parse Scru128Id
90        let parse_id = |s: &str, name: &str| -> Result<scru128::Scru128Id, ShellError> {
91            s.parse().map_err(|e| {
92                ShellError::Generic(GenericError::new(
93                    format!("Invalid {name}"),
94                    format!("Failed to parse Scru128Id: {e}"),
95                    call.head,
96                ))
97            })
98        };
99
100        let after: Option<scru128::Scru128Id> =
101            after.as_deref().map(|s| parse_id(s, "after")).transpose()?;
102        let from: Option<scru128::Scru128Id> =
103            from.as_deref().map(|s| parse_id(s, "from")).transpose()?;
104
105        let options = ReadOptions::builder()
106            .maybe_after(after)
107            .maybe_from(from)
108            .maybe_limit(limit)
109            .maybe_last(last)
110            .maybe_topic(topic)
111            .build();
112
113        let frames: Vec<_> = self.store.read_sync(options).collect();
114
115        use nu_protocol::Value;
116
117        let output = Value::list(
118            frames
119                .into_iter()
120                .map(|frame| crate::nu::util::frame_to_value(&frame, call.head, with_timestamp))
121                .collect(),
122            call.head,
123        );
124
125        Ok(PipelineData::Value(output, None))
126    }
127}