Skip to main content

xs/nu/commands/
last_command.rs

1use nu_engine::CallExt;
2use nu_protocol::engine::{Call, Command, EngineState, Stack};
3use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value};
4
5use crate::nu::util;
6use crate::store::{ReadOptions, Store};
7
8#[derive(Clone)]
9pub struct LastCommand {
10    store: Store,
11}
12
13impl LastCommand {
14    pub fn new(store: Store) -> Self {
15        Self { store }
16    }
17}
18
19impl Command for LastCommand {
20    fn name(&self) -> &str {
21        ".last"
22    }
23
24    fn signature(&self) -> Signature {
25        Signature::build(".last")
26            .input_output_types(vec![(Type::Nothing, Type::Any)])
27            .optional(
28                "topic",
29                SyntaxShape::Any,
30                "topic pattern(s) to get most recent frames from: string (commas allowed) or list (default: all topics)",
31            )
32            .optional(
33                "count",
34                SyntaxShape::Int,
35                "number of frames to return (default: 1)",
36            )
37            .switch(
38                "with-timestamp",
39                "include timestamp extracted from frame ID",
40                None,
41            )
42            .category(Category::Experimental)
43    }
44
45    fn description(&self) -> &str {
46        "get the most recent frame(s) for a topic"
47    }
48
49    fn run(
50        &self,
51        engine_state: &EngineState,
52        stack: &mut Stack,
53        call: &Call,
54        _input: PipelineData,
55    ) -> Result<PipelineData, ShellError> {
56        let raw_topic: Option<Value> = call.opt(engine_state, stack, 0)?;
57        let raw_count: Option<i64> = call.opt(engine_state, stack, 1)?;
58        let with_timestamp = call.has_flag(engine_state, stack, "with-timestamp")?;
59        let span = call.head;
60
61        // Disambiguate: if topic is an integer (or parses as one) and count is
62        // absent, treat it as the count (topics cannot start with digits per
63        // ADR 0002)
64        let (topic, n) = match (raw_topic, raw_count) {
65            (Some(Value::Int { val, .. }), None) if val > 0 => (None, val as usize),
66            (Some(Value::String { val, .. }), None) if val.parse::<usize>().is_ok() => {
67                (None, val.parse::<usize>().unwrap())
68            }
69            (raw_topic, raw_count) => (
70                raw_topic.map(util::topic_value_to_string).transpose()?,
71                raw_count.map(|v| v as usize).unwrap_or(1),
72            ),
73        };
74
75        let options = ReadOptions::builder().last(n).maybe_topic(topic).build();
76
77        let frames: Vec<Value> = self
78            .store
79            .read_sync(options)
80            .map(|frame| util::frame_to_value(&frame, span, with_timestamp))
81            .collect();
82
83        if frames.is_empty() {
84            Ok(PipelineData::Empty)
85        } else if frames.len() == 1 {
86            Ok(PipelineData::Value(
87                frames.into_iter().next().unwrap(),
88                None,
89            ))
90        } else {
91            Ok(PipelineData::Value(Value::list(frames, span), None))
92        }
93    }
94}