nu_cmd_lang/core_commands/
version.rs1use std::{borrow::Cow, sync::OnceLock};
2
3use itertools::Itertools;
4use nu_engine::command_prelude::*;
5use nu_protocol::engine::StateWorkingSet;
6use shadow_rs::shadow;
7
8shadow!(build);
9
10pub static VERSION: OnceLock<semver::Version> = OnceLock::new();
28
29pub static VERSION_NU_FEATURES: OnceLock<Vec<Cow<'static, str>>> = OnceLock::new();
64
65#[derive(Clone)]
66pub struct Version;
67
68impl Command for Version {
69 fn name(&self) -> &str {
70 "version"
71 }
72
73 fn signature(&self) -> Signature {
74 Signature::build("version")
75 .input_output_types(vec![(Type::Nothing, Type::record())])
76 .allow_variants_without_examples(true)
77 .category(Category::Core)
78 }
79
80 fn description(&self) -> &str {
81 "Display Nu version, and its build configuration."
82 }
83
84 fn is_const(&self) -> bool {
85 true
86 }
87
88 fn run(
89 &self,
90 engine_state: &EngineState,
91 _stack: &mut Stack,
92 call: &Call,
93 _input: PipelineData,
94 ) -> Result<PipelineData, ShellError> {
95 version(engine_state, call.head)
96 }
97
98 fn run_const(
99 &self,
100 working_set: &StateWorkingSet,
101 call: &Call,
102 _input: PipelineData,
103 ) -> Result<PipelineData, ShellError> {
104 version(working_set.permanent(), call.head)
105 }
106
107 fn examples(&self) -> Vec<Example<'_>> {
108 vec![Example {
109 description: "Display Nu version.",
110 example: "version",
111 result: None,
112 }]
113 }
114}
115
116fn push_non_empty(record: &mut Record, name: &str, value: &str, span: Span) {
117 if !value.is_empty() {
118 record.push(name, Value::string(value, span))
119 }
120}
121
122fn load_version() -> Cow<'static, semver::Version> {
123 match VERSION.get() {
124 Some(version) => Cow::Borrowed(version),
125 None => Cow::Owned(
126 semver::Version::parse(env!("CARGO_PKG_VERSION")).expect("cargo sets valid version"),
127 ),
128 }
129}
130
131pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, ShellError> {
132 const VERSION_MAX_ROWS: usize = [
134 "version",
135 "major",
136 "minor",
137 "patch",
138 "pre",
139 "build",
140 "branch",
141 "commit_hash",
142 "build_os",
143 "build_target",
144 "rust_version",
145 "rust_channel",
146 "cargo_version",
147 "build_time",
148 "build_rust_channel",
149 "allocator",
150 "features",
151 "installed_plugins",
152 ]
153 .len();
154
155 let mut record = Record::with_capacity(VERSION_MAX_ROWS);
156
157 let version = load_version();
158 record.push("version", Value::string(version.to_string(), span));
159 record.push("major", Value::int(version.major as i64, span));
160 record.push("minor", Value::int(version.minor as i64, span));
161 record.push("patch", Value::int(version.patch as i64, span));
162 push_non_empty(&mut record, "pre", version.pre.as_str(), span);
163 push_non_empty(&mut record, "build", version.build.as_str(), span);
164
165 record.push("branch", Value::string(build::BRANCH, span));
166
167 if let Some(commit_hash) = option_env!("NU_COMMIT_HASH") {
168 record.push("commit_hash", Value::string(commit_hash, span));
169 }
170
171 push_non_empty(&mut record, "build_os", build::BUILD_OS, span);
172 push_non_empty(&mut record, "build_target", build::BUILD_TARGET, span);
173 push_non_empty(&mut record, "rust_version", build::RUST_VERSION, span);
174 push_non_empty(&mut record, "rust_channel", build::RUST_CHANNEL, span);
175 push_non_empty(&mut record, "cargo_version", build::CARGO_VERSION, span);
176 push_non_empty(&mut record, "build_time", build::BUILD_TIME, span);
177 push_non_empty(
178 &mut record,
179 "build_rust_channel",
180 build::BUILD_RUST_CHANNEL,
181 span,
182 );
183
184 record.push("allocator", Value::string(global_allocator(), span));
185
186 record.push(
187 "features",
188 Value::string(
189 VERSION_NU_FEATURES
190 .get()
191 .as_ref()
192 .map(|v| v.as_slice())
193 .unwrap_or_default()
194 .iter()
195 .filter(|f| !f.starts_with("dep:"))
196 .join(", "),
197 span,
198 ),
199 );
200
201 #[cfg(not(feature = "plugin"))]
202 let _ = engine_state;
203
204 #[cfg(feature = "plugin")]
205 {
206 let installed_plugins = engine_state
208 .plugins()
209 .iter()
210 .map(|x| {
211 let name = x.identity().name();
212 if let Some(version) = x.metadata().and_then(|m| m.version) {
213 format!("{name} {version}")
214 } else {
215 name.into()
216 }
217 })
218 .collect::<Vec<_>>();
219
220 record.push(
221 "installed_plugins",
222 Value::string(installed_plugins.join(", "), span),
223 );
224 }
225
226 record.push(
227 "experimental_options",
228 Value::string(
229 nu_experimental::ALL
230 .iter()
231 .map(|option| format!("{}={}", option.identifier(), option.get()))
232 .join(", "),
233 span,
234 ),
235 );
236
237 Ok(Value::record(record, span).into_pipeline_data())
238}
239
240fn global_allocator() -> &'static str {
241 "standard"
242}
243
244#[cfg(test)]
245mod test {
246 #[test]
247 fn test_examples() -> nu_test_support::Result {
248 use super::Version;
249 nu_test_support::test().examples(Version)
250 }
251}