mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Default plugins are independent and called from Nu. (#1322)
This commit is contained in:
parent
4e201d20ca
commit
3610baa227
27 changed files with 805 additions and 1511 deletions
46
Cargo.toml
46
Cargo.toml
|
@ -40,24 +40,24 @@ members = [
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-source = {version = "0.9.0", path = "./crates/nu-source"}
|
nu-source = { version = "0.9.0", path = "./crates/nu-source" }
|
||||||
nu-plugin = {version = "0.9.0", path = "./crates/nu-plugin"}
|
nu-plugin = { version = "0.9.0", path = "./crates/nu-plugin" }
|
||||||
nu-protocol = {version = "0.9.0", path = "./crates/nu-protocol"}
|
nu-protocol = { version = "0.9.0", path = "./crates/nu-protocol" }
|
||||||
nu-errors = {version = "0.9.0", path = "./crates/nu-errors"}
|
nu-errors = { version = "0.9.0", path = "./crates/nu-errors" }
|
||||||
nu-parser = {version = "0.9.0", path = "./crates/nu-parser"}
|
nu-parser = { version = "0.9.0", path = "./crates/nu-parser" }
|
||||||
nu-value-ext = {version = "0.9.0", path = "./crates/nu-value-ext"}
|
nu-value-ext = { version = "0.9.0", path = "./crates/nu-value-ext" }
|
||||||
nu_plugin_average = {version = "0.9.0", path = "./crates/nu_plugin_average", optional=true}
|
nu_plugin_average = { version = "0.9.0", path = "./crates/nu_plugin_average", optional=true }
|
||||||
nu_plugin_binaryview = {version = "0.9.0", path = "./crates/nu_plugin_binaryview", optional=true}
|
nu_plugin_binaryview = { version = "0.9.0", path = "./crates/nu_plugin_binaryview", optional=true }
|
||||||
nu_plugin_fetch = {version = "0.9.0", path = "./crates/nu_plugin_fetch", optional=true}
|
nu_plugin_fetch = { version = "0.9.0", path = "./crates/nu_plugin_fetch", optional=true }
|
||||||
nu_plugin_inc = {version = "0.9.0", path = "./crates/nu_plugin_inc", optional=true}
|
nu_plugin_inc = { version = "0.9.0", path = "./crates/nu_plugin_inc", optional=true }
|
||||||
nu_plugin_match = {version = "0.9.0", path = "./crates/nu_plugin_match", optional=true}
|
nu_plugin_match = { version = "0.9.0", path = "./crates/nu_plugin_match", optional=true }
|
||||||
nu_plugin_post = {version = "0.9.0", path = "./crates/nu_plugin_post", optional=true}
|
nu_plugin_post = { version = "0.9.0", path = "./crates/nu_plugin_post", optional=true }
|
||||||
nu_plugin_ps = {version = "0.9.0", path = "./crates/nu_plugin_ps", optional=true}
|
nu_plugin_ps = { version = "0.9.0", path = "./crates/nu_plugin_ps", optional=true }
|
||||||
nu_plugin_str = {version = "0.9.0", path = "./crates/nu_plugin_str", optional=true}
|
nu_plugin_str = { version = "0.9.0", path = "./crates/nu_plugin_str", optional=true }
|
||||||
nu_plugin_sum = {version = "0.9.0", path = "./crates/nu_plugin_sum", optional=true}
|
nu_plugin_sum = { version = "0.9.0", path = "./crates/nu_plugin_sum", optional=true }
|
||||||
nu_plugin_sys = {version = "0.9.0", path = "./crates/nu_plugin_sys", optional=true}
|
nu_plugin_sys = { version = "0.9.0", path = "./crates/nu_plugin_sys", optional=true }
|
||||||
nu_plugin_textview = {version = "0.9.0", path = "./crates/nu_plugin_textview", optional=true}
|
nu_plugin_textview = { version = "0.9.0", path = "./crates/nu_plugin_textview", optional=true }
|
||||||
nu_plugin_tree = {version = "0.9.0", path = "./crates/nu_plugin_tree", optional=true}
|
nu_plugin_tree = { version = "0.9.0", path = "./crates/nu_plugin_tree", optional=true }
|
||||||
nu-macros = { version = "0.9.0", path = "./crates/nu-macros" }
|
nu-macros = { version = "0.9.0", path = "./crates/nu-macros" }
|
||||||
|
|
||||||
query_interface = "0.3.5"
|
query_interface = "0.3.5"
|
||||||
|
@ -123,9 +123,9 @@ futures-util = "0.3.1"
|
||||||
termcolor = "1.1.0"
|
termcolor = "1.1.0"
|
||||||
natural = "0.3.0"
|
natural = "0.3.0"
|
||||||
parking_lot = "0.10.0"
|
parking_lot = "0.10.0"
|
||||||
futures-timer = "1.0.2"
|
|
||||||
meval = "0.2"
|
meval = "0.2"
|
||||||
|
|
||||||
|
futures-timer = { version = "1.0.2", optional = true }
|
||||||
clipboard = {version = "0.5", optional = true }
|
clipboard = {version = "0.5", optional = true }
|
||||||
ptree = {version = "0.2" }
|
ptree = {version = "0.2" }
|
||||||
starship = { version = "0.33.1", optional = true}
|
starship = { version = "0.33.1", optional = true}
|
||||||
|
@ -148,10 +148,10 @@ default = ["sys", "ps", "textview", "inc", "str"]
|
||||||
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "sum", "post", "fetch", "clipboard"]
|
stable = ["default", "starship-prompt", "binaryview", "match", "tree", "average", "sum", "post", "fetch", "clipboard"]
|
||||||
|
|
||||||
# Default
|
# Default
|
||||||
sys = ["heim", "battery"]
|
textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"]
|
||||||
ps = ["heim"]
|
sys = ["heim", "battery", "nu_plugin_sys"]
|
||||||
textview = ["crossterm", "syntect", "onig_sys", "url"]
|
ps = ["heim", "futures-timer", "nu_plugin_ps"]
|
||||||
inc = ["nu_plugin_inc"]
|
inc = ["semver", "nu_plugin_inc"]
|
||||||
str = ["nu_plugin_str"]
|
str = ["nu_plugin_str"]
|
||||||
|
|
||||||
# Stable
|
# Stable
|
||||||
|
|
|
@ -862,7 +862,7 @@ pub fn delimited_brace(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
TokenTreeBuilder::spanned_square(tokens.item, (left, right), tokens.span),
|
TokenTreeBuilder::spanned_brace(tokens.item, (left, right), tokens.span),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
||||||
nu-source = { path = "../nu-source", version = "0.9.0" }
|
nu-source = { path = "../nu-source", version = "0.9.0" }
|
||||||
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
||||||
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
|
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
|
||||||
|
|
||||||
semver = "0.9.0"
|
semver = "0.9.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod inc;
|
mod inc;
|
||||||
mod nu_plugin_inc;
|
mod nu;
|
||||||
|
|
||||||
pub use inc::Inc;
|
pub use inc::Inc;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@ edition = "2018"
|
||||||
description = "A process list plugin for Nushell"
|
description = "A process list plugin for Nushell"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-plugin = { path = "../nu-plugin", version = "0.9.0" }
|
nu-plugin = { path = "../nu-plugin", version = "0.9.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
||||||
|
|
4
crates/nu_plugin_ps/src/lib.rs
Normal file
4
crates/nu_plugin_ps/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod nu;
|
||||||
|
mod ps;
|
||||||
|
|
||||||
|
pub use ps::Ps;
|
|
@ -1,94 +1,6 @@
|
||||||
use futures::executor::block_on;
|
use nu_plugin::serve_plugin;
|
||||||
//use futures::stream::TryStreamExt;
|
use nu_plugin_ps::Ps;
|
||||||
|
|
||||||
use futures_util::{StreamExt, TryStreamExt};
|
|
||||||
use heim::process::{self as process, Process, ProcessResult};
|
|
||||||
use heim::units::{information, ratio, Ratio};
|
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{
|
|
||||||
CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
struct Ps;
|
|
||||||
impl Ps {
|
|
||||||
fn new() -> Ps {
|
|
||||||
Ps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn usage(process: Process) -> ProcessResult<(process::Process, Ratio, process::Memory)> {
|
|
||||||
let usage_1 = process.cpu_usage().await?;
|
|
||||||
futures_timer::Delay::new(Duration::from_millis(100)).await;
|
|
||||||
let usage_2 = process.cpu_usage().await?;
|
|
||||||
|
|
||||||
let memory = process.memory().await?;
|
|
||||||
|
|
||||||
Ok((process, usage_2 - usage_1, memory))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ps(tag: Tag) -> Vec<Value> {
|
|
||||||
let processes = process::processes()
|
|
||||||
.map_ok(|process| {
|
|
||||||
// Note that there is no `.await` here,
|
|
||||||
// as we want to pass the returned future
|
|
||||||
// into the `.try_buffer_unordered`.
|
|
||||||
usage(process)
|
|
||||||
})
|
|
||||||
.try_buffer_unordered(usize::MAX);
|
|
||||||
pin_utils::pin_mut!(processes);
|
|
||||||
|
|
||||||
let mut output = vec![];
|
|
||||||
while let Some(res) = processes.next().await {
|
|
||||||
if let Ok((process, usage, memory)) = res {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
dict.insert_untagged("pid", UntaggedValue::int(process.pid()));
|
|
||||||
if let Ok(name) = process.name().await {
|
|
||||||
dict.insert_untagged("name", UntaggedValue::string(name));
|
|
||||||
}
|
|
||||||
if let Ok(status) = process.status().await {
|
|
||||||
dict.insert_untagged("status", UntaggedValue::string(format!("{:?}", status)));
|
|
||||||
}
|
|
||||||
dict.insert_untagged("cpu", UntaggedValue::decimal(usage.get::<ratio::percent>()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mem",
|
|
||||||
UntaggedValue::bytes(memory.rss().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"virtual",
|
|
||||||
UntaggedValue::bytes(memory.vms().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for Ps {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("ps")
|
|
||||||
.desc("View information about system processes.")
|
|
||||||
.filter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(block_on(ps(callinfo.name_tag))
|
|
||||||
.into_iter()
|
|
||||||
.map(ReturnSuccess::value)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut Ps::new());
|
serve_plugin(&mut Ps::new())
|
||||||
}
|
}
|
||||||
|
|
25
crates/nu_plugin_ps/src/nu/mod.rs
Normal file
25
crates/nu_plugin_ps/src/nu/mod.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::ps::{ps, Ps};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::Plugin;
|
||||||
|
use nu_protocol::{CallInfo, ReturnSuccess, ReturnValue, Signature, Value};
|
||||||
|
|
||||||
|
use futures::executor::block_on;
|
||||||
|
|
||||||
|
impl Plugin for Ps {
|
||||||
|
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||||
|
Ok(Signature::build("ps")
|
||||||
|
.desc("View information about system processes.")
|
||||||
|
.filter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
Ok(block_on(ps(callinfo.name_tag))
|
||||||
|
.into_iter()
|
||||||
|
.map(ReturnSuccess::value)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
66
crates/nu_plugin_ps/src/ps.rs
Normal file
66
crates/nu_plugin_ps/src/ps.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use futures_util::{StreamExt, TryStreamExt};
|
||||||
|
use heim::process::{self as process, Process, ProcessResult};
|
||||||
|
use heim::units::{information, ratio, Ratio};
|
||||||
|
use std::usize;
|
||||||
|
|
||||||
|
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||||
|
use nu_source::Tag;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Ps;
|
||||||
|
|
||||||
|
impl Ps {
|
||||||
|
pub fn new() -> Ps {
|
||||||
|
Ps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn usage(process: Process) -> ProcessResult<(process::Process, Ratio, process::Memory)> {
|
||||||
|
let usage_1 = process.cpu_usage().await?;
|
||||||
|
futures_timer::Delay::new(Duration::from_millis(100)).await;
|
||||||
|
let usage_2 = process.cpu_usage().await?;
|
||||||
|
|
||||||
|
let memory = process.memory().await?;
|
||||||
|
|
||||||
|
Ok((process, usage_2 - usage_1, memory))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ps(tag: Tag) -> Vec<Value> {
|
||||||
|
let processes = process::processes()
|
||||||
|
.map_ok(|process| {
|
||||||
|
// Note that there is no `.await` here,
|
||||||
|
// as we want to pass the returned future
|
||||||
|
// into the `.try_buffer_unordered`.
|
||||||
|
usage(process)
|
||||||
|
})
|
||||||
|
.try_buffer_unordered(usize::MAX);
|
||||||
|
pin_utils::pin_mut!(processes);
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
while let Some(res) = processes.next().await {
|
||||||
|
if let Ok((process, usage, memory)) = res {
|
||||||
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
dict.insert_untagged("pid", UntaggedValue::int(process.pid()));
|
||||||
|
if let Ok(name) = process.name().await {
|
||||||
|
dict.insert_untagged("name", UntaggedValue::string(name));
|
||||||
|
}
|
||||||
|
if let Ok(status) = process.status().await {
|
||||||
|
dict.insert_untagged("status", UntaggedValue::string(format!("{:?}", status)));
|
||||||
|
}
|
||||||
|
dict.insert_untagged("cpu", UntaggedValue::decimal(usage.get::<ratio::percent>()));
|
||||||
|
dict.insert_untagged(
|
||||||
|
"mem",
|
||||||
|
UntaggedValue::bytes(memory.rss().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"virtual",
|
||||||
|
UntaggedValue::bytes(memory.vms().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
output.push(dict.into_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
||||||
nu-source = { path = "../nu-source", version = "0.9.0" }
|
nu-source = { path = "../nu-source", version = "0.9.0" }
|
||||||
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
||||||
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
|
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
|
||||||
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
num-bigint = "0.2.3"
|
num-bigint = "0.2.3"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod nu_plugin_str;
|
mod nu;
|
||||||
mod strutils;
|
mod strutils;
|
||||||
|
|
||||||
pub use strutils::Str;
|
pub use strutils::Str;
|
||||||
|
|
|
@ -11,6 +11,7 @@ nu-plugin = { path = "../nu-plugin", version = "0.9.0" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
|
||||||
nu-source = { path = "../nu-source", version = "0.9.0" }
|
nu-source = { path = "../nu-source", version = "0.9.0" }
|
||||||
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
nu-errors = { path = "../nu-errors", version = "0.9.0" }
|
||||||
|
|
||||||
heim = "0.0.9"
|
heim = "0.0.9"
|
||||||
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
|
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
|
||||||
battery = "0.7.5"
|
battery = "0.7.5"
|
||||||
|
|
4
crates/nu_plugin_sys/src/lib.rs
Normal file
4
crates/nu_plugin_sys/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod nu;
|
||||||
|
mod sys;
|
||||||
|
|
||||||
|
pub use sys::Sys;
|
|
@ -1,354 +1,5 @@
|
||||||
use std::ffi::OsStr;
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_sys::Sys;
|
||||||
use futures::executor::block_on;
|
|
||||||
//use futures::stream::StreamExt;
|
|
||||||
use futures_util::StreamExt;
|
|
||||||
use heim::units::{frequency, information, thermodynamic_temperature, time};
|
|
||||||
use heim::{disk, host, memory, net, sensors};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{
|
|
||||||
CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
|
||||||
|
|
||||||
struct Sys;
|
|
||||||
impl Sys {
|
|
||||||
fn new() -> Sys {
|
|
||||||
Sys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cpu(tag: Tag) -> Option<Value> {
|
|
||||||
match futures::future::try_join(heim::cpu::logical_count(), heim::cpu::frequency()).await {
|
|
||||||
Ok((num_cpu, cpu_speed)) => {
|
|
||||||
let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4);
|
|
||||||
cpu_idx.insert_untagged("cores", UntaggedValue::int(num_cpu));
|
|
||||||
|
|
||||||
let current_speed =
|
|
||||||
(cpu_speed.current().get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0)
|
|
||||||
.round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("current ghz", UntaggedValue::decimal(current_speed));
|
|
||||||
|
|
||||||
if let Some(min_speed) = cpu_speed.min() {
|
|
||||||
let min_speed =
|
|
||||||
(min_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("min ghz", UntaggedValue::decimal(min_speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(max_speed) = cpu_speed.max() {
|
|
||||||
let max_speed =
|
|
||||||
(max_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("max ghz", UntaggedValue::decimal(max_speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(cpu_idx.into_value())
|
|
||||||
}
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn mem(tag: Tag) -> Value {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(tag, 4);
|
|
||||||
|
|
||||||
let (memory_result, swap_result) =
|
|
||||||
futures::future::join(memory::memory(), memory::swap()).await;
|
|
||||||
|
|
||||||
if let Ok(memory) = memory_result {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"total",
|
|
||||||
UntaggedValue::bytes(memory.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"free",
|
|
||||||
UntaggedValue::bytes(memory.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(swap) = swap_result {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"swap total",
|
|
||||||
UntaggedValue::bytes(swap.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"swap free",
|
|
||||||
UntaggedValue::bytes(swap.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dict.into_value()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn host(tag: Tag) -> Value {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
|
||||||
|
|
||||||
let (platform_result, uptime_result) =
|
|
||||||
futures::future::join(host::platform(), host::uptime()).await;
|
|
||||||
|
|
||||||
// OS
|
|
||||||
if let Ok(platform) = platform_result {
|
|
||||||
dict.insert_untagged("name", UntaggedValue::string(platform.system()));
|
|
||||||
dict.insert_untagged("release", UntaggedValue::string(platform.release()));
|
|
||||||
dict.insert_untagged("hostname", UntaggedValue::string(platform.hostname()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"arch",
|
|
||||||
UntaggedValue::string(platform.architecture().as_str()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uptime
|
|
||||||
if let Ok(uptime) = uptime_result {
|
|
||||||
let mut uptime_dict = TaggedDictBuilder::with_capacity(&tag, 4);
|
|
||||||
|
|
||||||
let uptime = uptime.get::<time::second>().round() as i64;
|
|
||||||
let days = uptime / (60 * 60 * 24);
|
|
||||||
let hours = (uptime - days * 60 * 60 * 24) / (60 * 60);
|
|
||||||
let minutes = (uptime - days * 60 * 60 * 24 - hours * 60 * 60) / 60;
|
|
||||||
let seconds = uptime % 60;
|
|
||||||
|
|
||||||
uptime_dict.insert_untagged("days", UntaggedValue::int(days));
|
|
||||||
uptime_dict.insert_untagged("hours", UntaggedValue::int(hours));
|
|
||||||
uptime_dict.insert_untagged("mins", UntaggedValue::int(minutes));
|
|
||||||
uptime_dict.insert_untagged("secs", UntaggedValue::int(seconds));
|
|
||||||
|
|
||||||
dict.insert_value("uptime", uptime_dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users
|
|
||||||
let mut users = host::users();
|
|
||||||
let mut user_vec = vec![];
|
|
||||||
while let Some(user) = users.next().await {
|
|
||||||
if let Ok(user) = user {
|
|
||||||
user_vec.push(Value {
|
|
||||||
value: UntaggedValue::string(user.username()),
|
|
||||||
tag: tag.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let user_list = UntaggedValue::Table(user_vec);
|
|
||||||
dict.insert_untagged("users", user_list);
|
|
||||||
|
|
||||||
dict.into_value()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn disks(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
let mut partitions = disk::partitions_physical();
|
|
||||||
while let Some(part) = partitions.next().await {
|
|
||||||
if let Ok(part) = part {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"device",
|
|
||||||
UntaggedValue::string(
|
|
||||||
part.device()
|
|
||||||
.unwrap_or_else(|| OsStr::new("N/A"))
|
|
||||||
.to_string_lossy(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
dict.insert_untagged("type", UntaggedValue::string(part.file_system().as_str()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mount",
|
|
||||||
UntaggedValue::string(part.mount_point().to_string_lossy()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(usage) = disk::usage(part.mount_point().to_path_buf()).await {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"total",
|
|
||||||
UntaggedValue::bytes(usage.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"used",
|
|
||||||
UntaggedValue::bytes(usage.used().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"free",
|
|
||||||
UntaggedValue::bytes(usage.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn battery(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
if let Ok(manager) = battery::Manager::new() {
|
|
||||||
if let Ok(batteries) = manager.batteries() {
|
|
||||||
for battery in batteries {
|
|
||||||
if let Ok(battery) = battery {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
if let Some(vendor) = battery.vendor() {
|
|
||||||
dict.insert_untagged("vendor", UntaggedValue::string(vendor));
|
|
||||||
}
|
|
||||||
if let Some(model) = battery.model() {
|
|
||||||
dict.insert_untagged("model", UntaggedValue::string(model));
|
|
||||||
}
|
|
||||||
if let Some(cycles) = battery.cycle_count() {
|
|
||||||
dict.insert_untagged("cycles", UntaggedValue::int(cycles));
|
|
||||||
}
|
|
||||||
if let Some(time_to_full) = battery.time_to_full() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mins to full",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
time_to_full.get::<battery::units::time::minute>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(time_to_empty) = battery.time_to_empty() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mins to empty",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
time_to_empty.get::<battery::units::time::minute>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn temp(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
let mut sensors = sensors::temperatures();
|
|
||||||
while let Some(sensor) = sensors.next().await {
|
|
||||||
if let Ok(sensor) = sensor {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
dict.insert_untagged("unit", UntaggedValue::string(sensor.unit()));
|
|
||||||
if let Some(label) = sensor.label() {
|
|
||||||
dict.insert_untagged("label", UntaggedValue::string(label));
|
|
||||||
}
|
|
||||||
dict.insert_untagged(
|
|
||||||
"temp",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
sensor
|
|
||||||
.current()
|
|
||||||
.get::<thermodynamic_temperature::degree_celsius>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if let Some(high) = sensor.high() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"high",
|
|
||||||
UntaggedValue::decimal(high.get::<thermodynamic_temperature::degree_celsius>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(critical) = sensor.critical() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"critical",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
critical.get::<thermodynamic_temperature::degree_celsius>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn net(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
let mut io_counters = net::io_counters();
|
|
||||||
while let Some(nic) = io_counters.next().await {
|
|
||||||
if let Ok(nic) = nic {
|
|
||||||
let mut network_idx = TaggedDictBuilder::with_capacity(&tag, 3);
|
|
||||||
network_idx.insert_untagged("name", UntaggedValue::string(nic.interface()));
|
|
||||||
network_idx.insert_untagged(
|
|
||||||
"sent",
|
|
||||||
UntaggedValue::bytes(nic.bytes_sent().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
network_idx.insert_untagged(
|
|
||||||
"recv",
|
|
||||||
UntaggedValue::bytes(nic.bytes_recv().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
output.push(network_idx.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sysinfo(tag: Tag) -> Vec<Value> {
|
|
||||||
let mut sysinfo = TaggedDictBuilder::with_capacity(&tag, 7);
|
|
||||||
|
|
||||||
let (host, cpu, disks, memory, temp) = futures::future::join5(
|
|
||||||
host(tag.clone()),
|
|
||||||
cpu(tag.clone()),
|
|
||||||
disks(tag.clone()),
|
|
||||||
mem(tag.clone()),
|
|
||||||
temp(tag.clone()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let (net, battery) = futures::future::join(net(tag.clone()), battery(tag.clone())).await;
|
|
||||||
|
|
||||||
sysinfo.insert_value("host", host);
|
|
||||||
if let Some(cpu) = cpu {
|
|
||||||
sysinfo.insert_value("cpu", cpu);
|
|
||||||
}
|
|
||||||
if let Some(disks) = disks {
|
|
||||||
sysinfo.insert_untagged("disks", disks);
|
|
||||||
}
|
|
||||||
sysinfo.insert_value("mem", memory);
|
|
||||||
if let Some(temp) = temp {
|
|
||||||
sysinfo.insert_untagged("temp", temp);
|
|
||||||
}
|
|
||||||
if let Some(net) = net {
|
|
||||||
sysinfo.insert_untagged("net", net);
|
|
||||||
}
|
|
||||||
if let Some(battery) = battery {
|
|
||||||
sysinfo.insert_untagged("battery", battery);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![sysinfo.into_value()]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for Sys {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("sys")
|
|
||||||
.desc("View information about the current system.")
|
|
||||||
.filter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(block_on(sysinfo(callinfo.name_tag))
|
|
||||||
.into_iter()
|
|
||||||
.map(ReturnSuccess::value)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut Sys::new());
|
serve_plugin(&mut Sys::new());
|
||||||
|
|
25
crates/nu_plugin_sys/src/nu/mod.rs
Normal file
25
crates/nu_plugin_sys/src/nu/mod.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::sys::{sysinfo, Sys};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::Plugin;
|
||||||
|
use nu_protocol::{CallInfo, ReturnSuccess, ReturnValue, Signature, Value};
|
||||||
|
|
||||||
|
use futures::executor::block_on;
|
||||||
|
|
||||||
|
impl Plugin for Sys {
|
||||||
|
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||||
|
Ok(Signature::build("sys")
|
||||||
|
.desc("View information about the current system.")
|
||||||
|
.filter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
Ok(block_on(sysinfo(callinfo.name_tag))
|
||||||
|
.into_iter()
|
||||||
|
.map(ReturnSuccess::value)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
327
crates/nu_plugin_sys/src/sys.rs
Normal file
327
crates/nu_plugin_sys/src/sys.rs
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use heim::units::{frequency, information, thermodynamic_temperature, time};
|
||||||
|
use heim::{disk, host, memory, net, sensors};
|
||||||
|
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||||
|
use nu_source::Tag;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Sys;
|
||||||
|
|
||||||
|
impl Sys {
|
||||||
|
pub fn new() -> Sys {
|
||||||
|
Sys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cpu(tag: Tag) -> Option<Value> {
|
||||||
|
match futures::future::try_join(heim::cpu::logical_count(), heim::cpu::frequency()).await {
|
||||||
|
Ok((num_cpu, cpu_speed)) => {
|
||||||
|
let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4);
|
||||||
|
cpu_idx.insert_untagged("cores", UntaggedValue::int(num_cpu));
|
||||||
|
|
||||||
|
let current_speed =
|
||||||
|
(cpu_speed.current().get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0)
|
||||||
|
.round()
|
||||||
|
/ 100.0;
|
||||||
|
cpu_idx.insert_untagged("current ghz", UntaggedValue::decimal(current_speed));
|
||||||
|
|
||||||
|
if let Some(min_speed) = cpu_speed.min() {
|
||||||
|
let min_speed =
|
||||||
|
(min_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
||||||
|
/ 100.0;
|
||||||
|
cpu_idx.insert_untagged("min ghz", UntaggedValue::decimal(min_speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(max_speed) = cpu_speed.max() {
|
||||||
|
let max_speed =
|
||||||
|
(max_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
||||||
|
/ 100.0;
|
||||||
|
cpu_idx.insert_untagged("max ghz", UntaggedValue::decimal(max_speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(cpu_idx.into_value())
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mem(tag: Tag) -> Value {
|
||||||
|
let mut dict = TaggedDictBuilder::with_capacity(tag, 4);
|
||||||
|
|
||||||
|
let (memory_result, swap_result) =
|
||||||
|
futures::future::join(memory::memory(), memory::swap()).await;
|
||||||
|
|
||||||
|
if let Ok(memory) = memory_result {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"total",
|
||||||
|
UntaggedValue::bytes(memory.total().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"free",
|
||||||
|
UntaggedValue::bytes(memory.free().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(swap) = swap_result {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"swap total",
|
||||||
|
UntaggedValue::bytes(swap.total().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"swap free",
|
||||||
|
UntaggedValue::bytes(swap.free().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dict.into_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn host(tag: Tag) -> Value {
|
||||||
|
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
||||||
|
|
||||||
|
let (platform_result, uptime_result) =
|
||||||
|
futures::future::join(host::platform(), host::uptime()).await;
|
||||||
|
|
||||||
|
// OS
|
||||||
|
if let Ok(platform) = platform_result {
|
||||||
|
dict.insert_untagged("name", UntaggedValue::string(platform.system()));
|
||||||
|
dict.insert_untagged("release", UntaggedValue::string(platform.release()));
|
||||||
|
dict.insert_untagged("hostname", UntaggedValue::string(platform.hostname()));
|
||||||
|
dict.insert_untagged(
|
||||||
|
"arch",
|
||||||
|
UntaggedValue::string(platform.architecture().as_str()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uptime
|
||||||
|
if let Ok(uptime) = uptime_result {
|
||||||
|
let mut uptime_dict = TaggedDictBuilder::with_capacity(&tag, 4);
|
||||||
|
|
||||||
|
let uptime = uptime.get::<time::second>().round() as i64;
|
||||||
|
let days = uptime / (60 * 60 * 24);
|
||||||
|
let hours = (uptime - days * 60 * 60 * 24) / (60 * 60);
|
||||||
|
let minutes = (uptime - days * 60 * 60 * 24 - hours * 60 * 60) / 60;
|
||||||
|
let seconds = uptime % 60;
|
||||||
|
|
||||||
|
uptime_dict.insert_untagged("days", UntaggedValue::int(days));
|
||||||
|
uptime_dict.insert_untagged("hours", UntaggedValue::int(hours));
|
||||||
|
uptime_dict.insert_untagged("mins", UntaggedValue::int(minutes));
|
||||||
|
uptime_dict.insert_untagged("secs", UntaggedValue::int(seconds));
|
||||||
|
|
||||||
|
dict.insert_value("uptime", uptime_dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users
|
||||||
|
let mut users = host::users();
|
||||||
|
let mut user_vec = vec![];
|
||||||
|
while let Some(user) = users.next().await {
|
||||||
|
if let Ok(user) = user {
|
||||||
|
user_vec.push(Value {
|
||||||
|
value: UntaggedValue::string(user.username()),
|
||||||
|
tag: tag.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let user_list = UntaggedValue::Table(user_vec);
|
||||||
|
dict.insert_untagged("users", user_list);
|
||||||
|
|
||||||
|
dict.into_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn disks(tag: Tag) -> Option<UntaggedValue> {
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut partitions = disk::partitions_physical();
|
||||||
|
while let Some(part) = partitions.next().await {
|
||||||
|
if let Ok(part) = part {
|
||||||
|
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"device",
|
||||||
|
UntaggedValue::string(
|
||||||
|
part.device()
|
||||||
|
.unwrap_or_else(|| OsStr::new("N/A"))
|
||||||
|
.to_string_lossy(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
dict.insert_untagged("type", UntaggedValue::string(part.file_system().as_str()));
|
||||||
|
dict.insert_untagged(
|
||||||
|
"mount",
|
||||||
|
UntaggedValue::string(part.mount_point().to_string_lossy()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Ok(usage) = disk::usage(part.mount_point().to_path_buf()).await {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"total",
|
||||||
|
UntaggedValue::bytes(usage.total().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"used",
|
||||||
|
UntaggedValue::bytes(usage.used().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
dict.insert_untagged(
|
||||||
|
"free",
|
||||||
|
UntaggedValue::bytes(usage.free().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(dict.into_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Some(UntaggedValue::Table(output))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn battery(tag: Tag) -> Option<UntaggedValue> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
if let Ok(manager) = battery::Manager::new() {
|
||||||
|
if let Ok(batteries) = manager.batteries() {
|
||||||
|
for battery in batteries {
|
||||||
|
if let Ok(battery) = battery {
|
||||||
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
if let Some(vendor) = battery.vendor() {
|
||||||
|
dict.insert_untagged("vendor", UntaggedValue::string(vendor));
|
||||||
|
}
|
||||||
|
if let Some(model) = battery.model() {
|
||||||
|
dict.insert_untagged("model", UntaggedValue::string(model));
|
||||||
|
}
|
||||||
|
if let Some(cycles) = battery.cycle_count() {
|
||||||
|
dict.insert_untagged("cycles", UntaggedValue::int(cycles));
|
||||||
|
}
|
||||||
|
if let Some(time_to_full) = battery.time_to_full() {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"mins to full",
|
||||||
|
UntaggedValue::decimal(
|
||||||
|
time_to_full.get::<battery::units::time::minute>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(time_to_empty) = battery.time_to_empty() {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"mins to empty",
|
||||||
|
UntaggedValue::decimal(
|
||||||
|
time_to_empty.get::<battery::units::time::minute>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
output.push(dict.into_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Some(UntaggedValue::Table(output))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn temp(tag: Tag) -> Option<UntaggedValue> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
let mut sensors = sensors::temperatures();
|
||||||
|
while let Some(sensor) = sensors.next().await {
|
||||||
|
if let Ok(sensor) = sensor {
|
||||||
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
dict.insert_untagged("unit", UntaggedValue::string(sensor.unit()));
|
||||||
|
if let Some(label) = sensor.label() {
|
||||||
|
dict.insert_untagged("label", UntaggedValue::string(label));
|
||||||
|
}
|
||||||
|
dict.insert_untagged(
|
||||||
|
"temp",
|
||||||
|
UntaggedValue::decimal(
|
||||||
|
sensor
|
||||||
|
.current()
|
||||||
|
.get::<thermodynamic_temperature::degree_celsius>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if let Some(high) = sensor.high() {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"high",
|
||||||
|
UntaggedValue::decimal(high.get::<thermodynamic_temperature::degree_celsius>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(critical) = sensor.critical() {
|
||||||
|
dict.insert_untagged(
|
||||||
|
"critical",
|
||||||
|
UntaggedValue::decimal(
|
||||||
|
critical.get::<thermodynamic_temperature::degree_celsius>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(dict.into_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !output.is_empty() {
|
||||||
|
Some(UntaggedValue::Table(output))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn net(tag: Tag) -> Option<UntaggedValue> {
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut io_counters = net::io_counters();
|
||||||
|
while let Some(nic) = io_counters.next().await {
|
||||||
|
if let Ok(nic) = nic {
|
||||||
|
let mut network_idx = TaggedDictBuilder::with_capacity(&tag, 3);
|
||||||
|
network_idx.insert_untagged("name", UntaggedValue::string(nic.interface()));
|
||||||
|
network_idx.insert_untagged(
|
||||||
|
"sent",
|
||||||
|
UntaggedValue::bytes(nic.bytes_sent().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
network_idx.insert_untagged(
|
||||||
|
"recv",
|
||||||
|
UntaggedValue::bytes(nic.bytes_recv().get::<information::byte>()),
|
||||||
|
);
|
||||||
|
output.push(network_idx.into_value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !output.is_empty() {
|
||||||
|
Some(UntaggedValue::Table(output))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sysinfo(tag: Tag) -> Vec<Value> {
|
||||||
|
let mut sysinfo = TaggedDictBuilder::with_capacity(&tag, 7);
|
||||||
|
|
||||||
|
let (host, cpu, disks, memory, temp) = futures::future::join5(
|
||||||
|
host(tag.clone()),
|
||||||
|
cpu(tag.clone()),
|
||||||
|
disks(tag.clone()),
|
||||||
|
mem(tag.clone()),
|
||||||
|
temp(tag.clone()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let (net, battery) = futures::future::join(net(tag.clone()), battery(tag.clone())).await;
|
||||||
|
|
||||||
|
sysinfo.insert_value("host", host);
|
||||||
|
if let Some(cpu) = cpu {
|
||||||
|
sysinfo.insert_value("cpu", cpu);
|
||||||
|
}
|
||||||
|
if let Some(disks) = disks {
|
||||||
|
sysinfo.insert_untagged("disks", disks);
|
||||||
|
}
|
||||||
|
sysinfo.insert_value("mem", memory);
|
||||||
|
if let Some(temp) = temp {
|
||||||
|
sysinfo.insert_untagged("temp", temp);
|
||||||
|
}
|
||||||
|
if let Some(net) = net {
|
||||||
|
sysinfo.insert_untagged("net", net);
|
||||||
|
}
|
||||||
|
if let Some(battery) = battery {
|
||||||
|
sysinfo.insert_untagged("battery", battery);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![sysinfo.into_value()]
|
||||||
|
}
|
4
crates/nu_plugin_textview/src/lib.rs
Normal file
4
crates/nu_plugin_textview/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
mod nu;
|
||||||
|
mod textview;
|
||||||
|
|
||||||
|
pub use textview::TextView;
|
|
@ -1,304 +1,5 @@
|
||||||
use crossterm::{
|
use nu_plugin::serve_plugin;
|
||||||
event::{KeyCode, KeyEvent},
|
use nu_plugin_textview::TextView;
|
||||||
ExecutableCommand,
|
|
||||||
};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value};
|
|
||||||
use nu_source::AnchorLocation;
|
|
||||||
|
|
||||||
use syntect::easy::HighlightLines;
|
|
||||||
use syntect::highlighting::{Style, ThemeSet};
|
|
||||||
use syntect::parsing::SyntaxSet;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
enum DrawCommand {
|
|
||||||
DrawString(Style, String),
|
|
||||||
NextLine,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextView;
|
|
||||||
|
|
||||||
impl TextView {
|
|
||||||
fn new() -> TextView {
|
|
||||||
TextView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for TextView {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("textview").desc("Autoview of text data."))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) {
|
|
||||||
if !input.is_empty() {
|
|
||||||
view_text_value(&input[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint_textview(
|
|
||||||
draw_commands: &[DrawCommand],
|
|
||||||
starting_row: usize,
|
|
||||||
use_color_buffer: bool,
|
|
||||||
) -> usize {
|
|
||||||
let size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
|
||||||
|
|
||||||
// render
|
|
||||||
let mut pos = 0;
|
|
||||||
let width = size.0 as usize;
|
|
||||||
let height = size.1 as usize - 1;
|
|
||||||
let mut frame_buffer = vec![];
|
|
||||||
|
|
||||||
for command in draw_commands {
|
|
||||||
match command {
|
|
||||||
DrawCommand::DrawString(style, string) => {
|
|
||||||
for chr in string.chars() {
|
|
||||||
if chr == '\t' {
|
|
||||||
for _ in 0..8 {
|
|
||||||
frame_buffer.push((
|
|
||||||
' ',
|
|
||||||
style.foreground.r,
|
|
||||||
style.foreground.g,
|
|
||||||
style.foreground.b,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
pos += 8;
|
|
||||||
} else {
|
|
||||||
frame_buffer.push((
|
|
||||||
chr,
|
|
||||||
style.foreground.r,
|
|
||||||
style.foreground.g,
|
|
||||||
style.foreground.b,
|
|
||||||
));
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DrawCommand::NextLine => {
|
|
||||||
for _ in 0..(width - pos % width) {
|
|
||||||
frame_buffer.push((' ', 0, 0, 0));
|
|
||||||
}
|
|
||||||
pos += width - pos % width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_frame_buffer_rows = frame_buffer.len() / width;
|
|
||||||
let buffer_needs_scrolling = num_frame_buffer_rows > height;
|
|
||||||
|
|
||||||
// display
|
|
||||||
let mut ansi_strings = vec![];
|
|
||||||
let mut normal_chars = vec![];
|
|
||||||
|
|
||||||
for c in
|
|
||||||
&frame_buffer[starting_row * width..std::cmp::min(pos, (starting_row + height) * width)]
|
|
||||||
{
|
|
||||||
if use_color_buffer {
|
|
||||||
ansi_strings.push(ansi_term::Colour::RGB(c.1, c.2, c.3).paint(format!("{}", c.0)));
|
|
||||||
} else {
|
|
||||||
normal_chars.push(c.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer_needs_scrolling {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if use_color_buffer {
|
|
||||||
print!("{}", ansi_term::ANSIStrings(&ansi_strings));
|
|
||||||
} else {
|
|
||||||
let s: String = normal_chars.into_iter().collect();
|
|
||||||
print!("{}", s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer_needs_scrolling {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, size.1));
|
|
||||||
print!(
|
|
||||||
"{}",
|
|
||||||
ansi_term::Colour::Blue.paint("[ESC to quit, arrow keys to move]")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = std::io::stdout().flush();
|
|
||||||
|
|
||||||
num_frame_buffer_rows
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer: bool) {
|
|
||||||
let mut starting_row = 0;
|
|
||||||
|
|
||||||
if let Ok(_raw) = crossterm::terminal::enable_raw_mode() {
|
|
||||||
let mut size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
|
||||||
let height = size.1 as usize - 1;
|
|
||||||
|
|
||||||
let mut max_bottom_line = paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
|
|
||||||
// Only scroll if needed
|
|
||||||
if max_bottom_line > height as usize {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::Hide);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Ok(ev) = crossterm::event::read() {
|
|
||||||
if let crossterm::event::Event::Key(KeyEvent { code, modifiers }) = ev {
|
|
||||||
match code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
if starting_row > 0 {
|
|
||||||
starting_row -= 1;
|
|
||||||
max_bottom_line = paint_textview(
|
|
||||||
&draw_commands,
|
|
||||||
starting_row,
|
|
||||||
use_color_buffer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += 1;
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::Char('b')
|
|
||||||
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
|
||||||
{
|
|
||||||
starting_row -= std::cmp::min(height, starting_row);
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::PageUp => {
|
|
||||||
starting_row -= std::cmp::min(height, starting_row);
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::Char('f')
|
|
||||||
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
|
||||||
{
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += height;
|
|
||||||
|
|
||||||
if starting_row > (max_bottom_line - height) {
|
|
||||||
starting_row = max_bottom_line - height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::PageDown | KeyCode::Char(' ') => {
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += height;
|
|
||||||
|
|
||||||
if starting_row > (max_bottom_line - height) {
|
|
||||||
starting_row = max_bottom_line - height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(new_size) = crossterm::terminal::size() {
|
|
||||||
if size != new_size {
|
|
||||||
size = new_size;
|
|
||||||
let _ = std::io::stdout().execute(crossterm::terminal::Clear(
|
|
||||||
crossterm::terminal::ClearType::All,
|
|
||||||
));
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::Show);
|
|
||||||
|
|
||||||
let _ = crossterm::terminal::disable_raw_mode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outln!("");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_view(s: &str) {
|
|
||||||
let mut v = vec![];
|
|
||||||
for line in s.lines() {
|
|
||||||
v.push(DrawCommand::DrawString(Style::default(), line.to_string()));
|
|
||||||
v.push(DrawCommand::NextLine);
|
|
||||||
}
|
|
||||||
scroll_view_lines_if_needed(v, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_text_value(value: &Value) {
|
|
||||||
let value_anchor = value.anchor();
|
|
||||||
if let UntaggedValue::Primitive(Primitive::String(ref s)) = &value.value {
|
|
||||||
if let Some(source) = value_anchor {
|
|
||||||
let extension: Option<String> = match source {
|
|
||||||
AnchorLocation::File(file) => {
|
|
||||||
let path = Path::new(&file);
|
|
||||||
path.extension().map(|x| x.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
AnchorLocation::Url(url) => {
|
|
||||||
let url = url::Url::parse(&url);
|
|
||||||
if let Ok(url) = url {
|
|
||||||
if let Some(mut segments) = url.path_segments() {
|
|
||||||
if let Some(file) = segments.next_back() {
|
|
||||||
let path = Path::new(file);
|
|
||||||
path.extension().map(|x| x.to_string_lossy().to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//FIXME: this probably isn't correct
|
|
||||||
AnchorLocation::Source(_source) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
match extension {
|
|
||||||
Some(extension) => {
|
|
||||||
// Load these once at the start of your program
|
|
||||||
let ps: SyntaxSet =
|
|
||||||
syntect::dumps::from_binary(include_bytes!("assets/syntaxes.bin"));
|
|
||||||
|
|
||||||
if let Some(syntax) = ps.find_syntax_by_extension(&extension) {
|
|
||||||
let ts: ThemeSet =
|
|
||||||
syntect::dumps::from_binary(include_bytes!("assets/themes.bin"));
|
|
||||||
let mut h = HighlightLines::new(syntax, &ts.themes["OneHalfDark"]);
|
|
||||||
|
|
||||||
let mut v = vec![];
|
|
||||||
for line in s.lines() {
|
|
||||||
let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
|
|
||||||
|
|
||||||
for range in ranges {
|
|
||||||
v.push(DrawCommand::DrawString(range.0, range.1.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
v.push(DrawCommand::NextLine);
|
|
||||||
}
|
|
||||||
scroll_view_lines_if_needed(v, true);
|
|
||||||
} else {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut TextView::new());
|
serve_plugin(&mut TextView::new());
|
||||||
|
|
16
crates/nu_plugin_textview/src/nu/mod.rs
Normal file
16
crates/nu_plugin_textview/src/nu/mod.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::textview::{view_text_value, TextView};
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::Plugin;
|
||||||
|
use nu_protocol::{CallInfo, Signature, Value};
|
||||||
|
|
||||||
|
impl Plugin for TextView {
|
||||||
|
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||||
|
Ok(Signature::build("textview").desc("Autoview of text data."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) {
|
||||||
|
if !input.is_empty() {
|
||||||
|
view_text_value(&input[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
289
crates/nu_plugin_textview/src/textview.rs
Normal file
289
crates/nu_plugin_textview/src/textview.rs
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
use crossterm::{
|
||||||
|
event::{KeyCode, KeyEvent},
|
||||||
|
ExecutableCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
use nu_protocol::{Primitive, UntaggedValue, Value};
|
||||||
|
use nu_source::AnchorLocation;
|
||||||
|
|
||||||
|
use syntect::easy::HighlightLines;
|
||||||
|
use syntect::highlighting::{Style, ThemeSet};
|
||||||
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
enum DrawCommand {
|
||||||
|
DrawString(Style, String),
|
||||||
|
NextLine,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TextView;
|
||||||
|
|
||||||
|
impl TextView {
|
||||||
|
pub fn new() -> TextView {
|
||||||
|
TextView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_textview(
|
||||||
|
draw_commands: &[DrawCommand],
|
||||||
|
starting_row: usize,
|
||||||
|
use_color_buffer: bool,
|
||||||
|
) -> usize {
|
||||||
|
let size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
||||||
|
|
||||||
|
// render
|
||||||
|
let mut pos = 0;
|
||||||
|
let width = size.0 as usize;
|
||||||
|
let height = size.1 as usize - 1;
|
||||||
|
let mut frame_buffer = vec![];
|
||||||
|
|
||||||
|
for command in draw_commands {
|
||||||
|
match command {
|
||||||
|
DrawCommand::DrawString(style, string) => {
|
||||||
|
for chr in string.chars() {
|
||||||
|
if chr == '\t' {
|
||||||
|
for _ in 0..8 {
|
||||||
|
frame_buffer.push((
|
||||||
|
' ',
|
||||||
|
style.foreground.r,
|
||||||
|
style.foreground.g,
|
||||||
|
style.foreground.b,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pos += 8;
|
||||||
|
} else {
|
||||||
|
frame_buffer.push((
|
||||||
|
chr,
|
||||||
|
style.foreground.r,
|
||||||
|
style.foreground.g,
|
||||||
|
style.foreground.b,
|
||||||
|
));
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DrawCommand::NextLine => {
|
||||||
|
for _ in 0..(width - pos % width) {
|
||||||
|
frame_buffer.push((' ', 0, 0, 0));
|
||||||
|
}
|
||||||
|
pos += width - pos % width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_frame_buffer_rows = frame_buffer.len() / width;
|
||||||
|
let buffer_needs_scrolling = num_frame_buffer_rows > height;
|
||||||
|
|
||||||
|
// display
|
||||||
|
let mut ansi_strings = vec![];
|
||||||
|
let mut normal_chars = vec![];
|
||||||
|
|
||||||
|
for c in
|
||||||
|
&frame_buffer[starting_row * width..std::cmp::min(pos, (starting_row + height) * width)]
|
||||||
|
{
|
||||||
|
if use_color_buffer {
|
||||||
|
ansi_strings.push(ansi_term::Colour::RGB(c.1, c.2, c.3).paint(format!("{}", c.0)));
|
||||||
|
} else {
|
||||||
|
normal_chars.push(c.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer_needs_scrolling {
|
||||||
|
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if use_color_buffer {
|
||||||
|
print!("{}", ansi_term::ANSIStrings(&ansi_strings));
|
||||||
|
} else {
|
||||||
|
let s: String = normal_chars.into_iter().collect();
|
||||||
|
print!("{}", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer_needs_scrolling {
|
||||||
|
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, size.1));
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
ansi_term::Colour::Blue.paint("[ESC to quit, arrow keys to move]")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
|
||||||
|
num_frame_buffer_rows
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer: bool) {
|
||||||
|
let mut starting_row = 0;
|
||||||
|
|
||||||
|
if let Ok(_raw) = crossterm::terminal::enable_raw_mode() {
|
||||||
|
let mut size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
||||||
|
let height = size.1 as usize - 1;
|
||||||
|
|
||||||
|
let mut max_bottom_line = paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
|
||||||
|
// Only scroll if needed
|
||||||
|
if max_bottom_line > height as usize {
|
||||||
|
let _ = std::io::stdout().execute(crossterm::cursor::Hide);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok(ev) = crossterm::event::read() {
|
||||||
|
if let crossterm::event::Event::Key(KeyEvent { code, modifiers }) = ev {
|
||||||
|
match code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
if starting_row > 0 {
|
||||||
|
starting_row -= 1;
|
||||||
|
max_bottom_line = paint_textview(
|
||||||
|
&draw_commands,
|
||||||
|
starting_row,
|
||||||
|
use_color_buffer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
if starting_row < (max_bottom_line - height) {
|
||||||
|
starting_row += 1;
|
||||||
|
}
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
KeyCode::Char('b')
|
||||||
|
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
||||||
|
{
|
||||||
|
starting_row -= std::cmp::min(height, starting_row);
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
KeyCode::PageUp => {
|
||||||
|
starting_row -= std::cmp::min(height, starting_row);
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
KeyCode::Char('f')
|
||||||
|
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
||||||
|
{
|
||||||
|
if starting_row < (max_bottom_line - height) {
|
||||||
|
starting_row += height;
|
||||||
|
|
||||||
|
if starting_row > (max_bottom_line - height) {
|
||||||
|
starting_row = max_bottom_line - height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
KeyCode::PageDown | KeyCode::Char(' ') => {
|
||||||
|
if starting_row < (max_bottom_line - height) {
|
||||||
|
starting_row += height;
|
||||||
|
|
||||||
|
if starting_row > (max_bottom_line - height) {
|
||||||
|
starting_row = max_bottom_line - height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(new_size) = crossterm::terminal::size() {
|
||||||
|
if size != new_size {
|
||||||
|
size = new_size;
|
||||||
|
let _ = std::io::stdout().execute(crossterm::terminal::Clear(
|
||||||
|
crossterm::terminal::ClearType::All,
|
||||||
|
));
|
||||||
|
max_bottom_line =
|
||||||
|
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = std::io::stdout().execute(crossterm::cursor::Show);
|
||||||
|
|
||||||
|
let _ = crossterm::terminal::disable_raw_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_view(s: &str) {
|
||||||
|
let mut v = vec![];
|
||||||
|
for line in s.lines() {
|
||||||
|
v.push(DrawCommand::DrawString(Style::default(), line.to_string()));
|
||||||
|
v.push(DrawCommand::NextLine);
|
||||||
|
}
|
||||||
|
scroll_view_lines_if_needed(v, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_text_value(value: &Value) {
|
||||||
|
let value_anchor = value.anchor();
|
||||||
|
if let UntaggedValue::Primitive(Primitive::String(ref s)) = &value.value {
|
||||||
|
if let Some(source) = value_anchor {
|
||||||
|
let extension: Option<String> = match source {
|
||||||
|
AnchorLocation::File(file) => {
|
||||||
|
let path = Path::new(&file);
|
||||||
|
path.extension().map(|x| x.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
AnchorLocation::Url(url) => {
|
||||||
|
let url = url::Url::parse(&url);
|
||||||
|
if let Ok(url) = url {
|
||||||
|
if let Some(mut segments) = url.path_segments() {
|
||||||
|
if let Some(file) = segments.next_back() {
|
||||||
|
let path = Path::new(file);
|
||||||
|
path.extension().map(|x| x.to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//FIXME: this probably isn't correct
|
||||||
|
AnchorLocation::Source(_source) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match extension {
|
||||||
|
Some(extension) => {
|
||||||
|
// Load these once at the start of your program
|
||||||
|
let ps: SyntaxSet =
|
||||||
|
syntect::dumps::from_binary(include_bytes!("assets/syntaxes.bin"));
|
||||||
|
|
||||||
|
if let Some(syntax) = ps.find_syntax_by_extension(&extension) {
|
||||||
|
let ts: ThemeSet =
|
||||||
|
syntect::dumps::from_binary(include_bytes!("assets/themes.bin"));
|
||||||
|
let mut h = HighlightLines::new(syntax, &ts.themes["OneHalfDark"]);
|
||||||
|
|
||||||
|
let mut v = vec![];
|
||||||
|
for line in s.lines() {
|
||||||
|
let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
|
||||||
|
|
||||||
|
for range in ranges {
|
||||||
|
v.push(DrawCommand::DrawString(range.0, range.1.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
v.push(DrawCommand::NextLine);
|
||||||
|
}
|
||||||
|
scroll_view_lines_if_needed(v, true);
|
||||||
|
} else {
|
||||||
|
scroll_view(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
scroll_view(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scroll_view(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,93 +1,5 @@
|
||||||
use futures::executor::block_on;
|
use nu_plugin::serve_plugin;
|
||||||
//use futures::stream::TryStreamExt;
|
use nu_plugin_ps::Ps;
|
||||||
|
|
||||||
use futures_util::{StreamExt, TryStreamExt};
|
|
||||||
use heim::process::{self as process, Process, ProcessResult};
|
|
||||||
use heim::units::{information, ratio, Ratio};
|
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{
|
|
||||||
CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
struct Ps;
|
|
||||||
impl Ps {
|
|
||||||
fn new() -> Ps {
|
|
||||||
Ps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn usage(process: Process) -> ProcessResult<(process::Process, Ratio, process::Memory)> {
|
|
||||||
let usage_1 = process.cpu_usage().await?;
|
|
||||||
futures_timer::Delay::new(Duration::from_millis(100)).await;
|
|
||||||
let usage_2 = process.cpu_usage().await?;
|
|
||||||
|
|
||||||
let memory = process.memory().await?;
|
|
||||||
|
|
||||||
Ok((process, usage_2 - usage_1, memory))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ps(tag: Tag) -> Vec<Value> {
|
|
||||||
let processes = process::processes()
|
|
||||||
.map_ok(|process| {
|
|
||||||
// Note that there is no `.await` here,
|
|
||||||
// as we want to pass the returned future
|
|
||||||
// into the `.try_buffer_unordered`.
|
|
||||||
usage(process)
|
|
||||||
})
|
|
||||||
.try_buffer_unordered(usize::MAX);
|
|
||||||
pin_utils::pin_mut!(processes);
|
|
||||||
|
|
||||||
let mut output = vec![];
|
|
||||||
while let Some(res) = processes.next().await {
|
|
||||||
if let Ok((process, usage, memory)) = res {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
dict.insert_untagged("pid", UntaggedValue::int(process.pid()));
|
|
||||||
if let Ok(name) = process.name().await {
|
|
||||||
dict.insert_untagged("name", UntaggedValue::string(name));
|
|
||||||
}
|
|
||||||
if let Ok(status) = process.status().await {
|
|
||||||
dict.insert_untagged("status", UntaggedValue::string(format!("{:?}", status)));
|
|
||||||
}
|
|
||||||
dict.insert_untagged("cpu", UntaggedValue::decimal(usage.get::<ratio::percent>()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mem",
|
|
||||||
UntaggedValue::bytes(memory.rss().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"virtual",
|
|
||||||
UntaggedValue::bytes(memory.vms().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for Ps {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("ps")
|
|
||||||
.desc("View information about system processes.")
|
|
||||||
.filter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(block_on(ps(callinfo.name_tag))
|
|
||||||
.into_iter()
|
|
||||||
.map(ReturnSuccess::value)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut Ps::new());
|
serve_plugin(&mut Ps::new());
|
||||||
|
|
|
@ -1,354 +1,5 @@
|
||||||
use std::ffi::OsStr;
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_sys::Sys;
|
||||||
use futures::executor::block_on;
|
|
||||||
//use futures::stream::StreamExt;
|
|
||||||
use futures_util::StreamExt;
|
|
||||||
use heim::units::{frequency, information, thermodynamic_temperature, time};
|
|
||||||
use heim::{disk, host, memory, net, sensors};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{
|
|
||||||
CallInfo, ReturnSuccess, ReturnValue, Signature, TaggedDictBuilder, UntaggedValue, Value,
|
|
||||||
};
|
|
||||||
use nu_source::Tag;
|
|
||||||
|
|
||||||
struct Sys;
|
|
||||||
impl Sys {
|
|
||||||
fn new() -> Sys {
|
|
||||||
Sys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cpu(tag: Tag) -> Option<Value> {
|
|
||||||
match futures::future::try_join(heim::cpu::logical_count(), heim::cpu::frequency()).await {
|
|
||||||
Ok((num_cpu, cpu_speed)) => {
|
|
||||||
let mut cpu_idx = TaggedDictBuilder::with_capacity(tag, 4);
|
|
||||||
cpu_idx.insert_untagged("cores", UntaggedValue::int(num_cpu));
|
|
||||||
|
|
||||||
let current_speed =
|
|
||||||
(cpu_speed.current().get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0)
|
|
||||||
.round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("current ghz", UntaggedValue::decimal(current_speed));
|
|
||||||
|
|
||||||
if let Some(min_speed) = cpu_speed.min() {
|
|
||||||
let min_speed =
|
|
||||||
(min_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("min ghz", UntaggedValue::decimal(min_speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(max_speed) = cpu_speed.max() {
|
|
||||||
let max_speed =
|
|
||||||
(max_speed.get::<frequency::hertz>() as f64 / 1_000_000_000.0 * 100.0).round()
|
|
||||||
/ 100.0;
|
|
||||||
cpu_idx.insert_untagged("max ghz", UntaggedValue::decimal(max_speed));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(cpu_idx.into_value())
|
|
||||||
}
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn mem(tag: Tag) -> Value {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(tag, 4);
|
|
||||||
|
|
||||||
let (memory_result, swap_result) =
|
|
||||||
futures::future::join(memory::memory(), memory::swap()).await;
|
|
||||||
|
|
||||||
if let Ok(memory) = memory_result {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"total",
|
|
||||||
UntaggedValue::bytes(memory.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"free",
|
|
||||||
UntaggedValue::bytes(memory.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(swap) = swap_result {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"swap total",
|
|
||||||
UntaggedValue::bytes(swap.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"swap free",
|
|
||||||
UntaggedValue::bytes(swap.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dict.into_value()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn host(tag: Tag) -> Value {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
|
||||||
|
|
||||||
let (platform_result, uptime_result) =
|
|
||||||
futures::future::join(host::platform(), host::uptime()).await;
|
|
||||||
|
|
||||||
// OS
|
|
||||||
if let Ok(platform) = platform_result {
|
|
||||||
dict.insert_untagged("name", UntaggedValue::string(platform.system()));
|
|
||||||
dict.insert_untagged("release", UntaggedValue::string(platform.release()));
|
|
||||||
dict.insert_untagged("hostname", UntaggedValue::string(platform.hostname()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"arch",
|
|
||||||
UntaggedValue::string(platform.architecture().as_str()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uptime
|
|
||||||
if let Ok(uptime) = uptime_result {
|
|
||||||
let mut uptime_dict = TaggedDictBuilder::with_capacity(&tag, 4);
|
|
||||||
|
|
||||||
let uptime = uptime.get::<time::second>().round() as i64;
|
|
||||||
let days = uptime / (60 * 60 * 24);
|
|
||||||
let hours = (uptime - days * 60 * 60 * 24) / (60 * 60);
|
|
||||||
let minutes = (uptime - days * 60 * 60 * 24 - hours * 60 * 60) / 60;
|
|
||||||
let seconds = uptime % 60;
|
|
||||||
|
|
||||||
uptime_dict.insert_untagged("days", UntaggedValue::int(days));
|
|
||||||
uptime_dict.insert_untagged("hours", UntaggedValue::int(hours));
|
|
||||||
uptime_dict.insert_untagged("mins", UntaggedValue::int(minutes));
|
|
||||||
uptime_dict.insert_untagged("secs", UntaggedValue::int(seconds));
|
|
||||||
|
|
||||||
dict.insert_value("uptime", uptime_dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users
|
|
||||||
let mut users = host::users();
|
|
||||||
let mut user_vec = vec![];
|
|
||||||
while let Some(user) = users.next().await {
|
|
||||||
if let Ok(user) = user {
|
|
||||||
user_vec.push(Value {
|
|
||||||
value: UntaggedValue::string(user.username()),
|
|
||||||
tag: tag.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let user_list = UntaggedValue::Table(user_vec);
|
|
||||||
dict.insert_untagged("users", user_list);
|
|
||||||
|
|
||||||
dict.into_value()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn disks(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
let mut partitions = disk::partitions_physical();
|
|
||||||
while let Some(part) = partitions.next().await {
|
|
||||||
if let Ok(part) = part {
|
|
||||||
let mut dict = TaggedDictBuilder::with_capacity(&tag, 6);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"device",
|
|
||||||
UntaggedValue::string(
|
|
||||||
part.device()
|
|
||||||
.unwrap_or_else(|| OsStr::new("N/A"))
|
|
||||||
.to_string_lossy(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
dict.insert_untagged("type", UntaggedValue::string(part.file_system().as_str()));
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mount",
|
|
||||||
UntaggedValue::string(part.mount_point().to_string_lossy()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(usage) = disk::usage(part.mount_point().to_path_buf()).await {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"total",
|
|
||||||
UntaggedValue::bytes(usage.total().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"used",
|
|
||||||
UntaggedValue::bytes(usage.used().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
dict.insert_untagged(
|
|
||||||
"free",
|
|
||||||
UntaggedValue::bytes(usage.free().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn battery(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
if let Ok(manager) = battery::Manager::new() {
|
|
||||||
if let Ok(batteries) = manager.batteries() {
|
|
||||||
for battery in batteries {
|
|
||||||
if let Ok(battery) = battery {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
if let Some(vendor) = battery.vendor() {
|
|
||||||
dict.insert_untagged("vendor", UntaggedValue::string(vendor));
|
|
||||||
}
|
|
||||||
if let Some(model) = battery.model() {
|
|
||||||
dict.insert_untagged("model", UntaggedValue::string(model));
|
|
||||||
}
|
|
||||||
if let Some(cycles) = battery.cycle_count() {
|
|
||||||
dict.insert_untagged("cycles", UntaggedValue::int(cycles));
|
|
||||||
}
|
|
||||||
if let Some(time_to_full) = battery.time_to_full() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mins to full",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
time_to_full.get::<battery::units::time::minute>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(time_to_empty) = battery.time_to_empty() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"mins to empty",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
time_to_empty.get::<battery::units::time::minute>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn temp(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
let mut sensors = sensors::temperatures();
|
|
||||||
while let Some(sensor) = sensors.next().await {
|
|
||||||
if let Ok(sensor) = sensor {
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
dict.insert_untagged("unit", UntaggedValue::string(sensor.unit()));
|
|
||||||
if let Some(label) = sensor.label() {
|
|
||||||
dict.insert_untagged("label", UntaggedValue::string(label));
|
|
||||||
}
|
|
||||||
dict.insert_untagged(
|
|
||||||
"temp",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
sensor
|
|
||||||
.current()
|
|
||||||
.get::<thermodynamic_temperature::degree_celsius>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if let Some(high) = sensor.high() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"high",
|
|
||||||
UntaggedValue::decimal(high.get::<thermodynamic_temperature::degree_celsius>()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(critical) = sensor.critical() {
|
|
||||||
dict.insert_untagged(
|
|
||||||
"critical",
|
|
||||||
UntaggedValue::decimal(
|
|
||||||
critical.get::<thermodynamic_temperature::degree_celsius>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(dict.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn net(tag: Tag) -> Option<UntaggedValue> {
|
|
||||||
let mut output = vec![];
|
|
||||||
let mut io_counters = net::io_counters();
|
|
||||||
while let Some(nic) = io_counters.next().await {
|
|
||||||
if let Ok(nic) = nic {
|
|
||||||
let mut network_idx = TaggedDictBuilder::with_capacity(&tag, 3);
|
|
||||||
network_idx.insert_untagged("name", UntaggedValue::string(nic.interface()));
|
|
||||||
network_idx.insert_untagged(
|
|
||||||
"sent",
|
|
||||||
UntaggedValue::bytes(nic.bytes_sent().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
network_idx.insert_untagged(
|
|
||||||
"recv",
|
|
||||||
UntaggedValue::bytes(nic.bytes_recv().get::<information::byte>()),
|
|
||||||
);
|
|
||||||
output.push(network_idx.into_value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !output.is_empty() {
|
|
||||||
Some(UntaggedValue::Table(output))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sysinfo(tag: Tag) -> Vec<Value> {
|
|
||||||
let mut sysinfo = TaggedDictBuilder::with_capacity(&tag, 7);
|
|
||||||
|
|
||||||
let (host, cpu, disks, memory, temp) = futures::future::join5(
|
|
||||||
host(tag.clone()),
|
|
||||||
cpu(tag.clone()),
|
|
||||||
disks(tag.clone()),
|
|
||||||
mem(tag.clone()),
|
|
||||||
temp(tag.clone()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let (net, battery) = futures::future::join(net(tag.clone()), battery(tag.clone())).await;
|
|
||||||
|
|
||||||
sysinfo.insert_value("host", host);
|
|
||||||
if let Some(cpu) = cpu {
|
|
||||||
sysinfo.insert_value("cpu", cpu);
|
|
||||||
}
|
|
||||||
if let Some(disks) = disks {
|
|
||||||
sysinfo.insert_untagged("disks", disks);
|
|
||||||
}
|
|
||||||
sysinfo.insert_value("mem", memory);
|
|
||||||
if let Some(temp) = temp {
|
|
||||||
sysinfo.insert_untagged("temp", temp);
|
|
||||||
}
|
|
||||||
if let Some(net) = net {
|
|
||||||
sysinfo.insert_untagged("net", net);
|
|
||||||
}
|
|
||||||
if let Some(battery) = battery {
|
|
||||||
sysinfo.insert_untagged("battery", battery);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![sysinfo.into_value()]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for Sys {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("sys")
|
|
||||||
.desc("View information about the current system.")
|
|
||||||
.filter())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(block_on(sysinfo(callinfo.name_tag))
|
|
||||||
.into_iter()
|
|
||||||
.map(ReturnSuccess::value)
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter(&mut self, _: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut Sys::new());
|
serve_plugin(&mut Sys::new());
|
||||||
|
|
|
@ -1,304 +1,5 @@
|
||||||
use crossterm::{
|
use nu_plugin::serve_plugin;
|
||||||
event::{KeyCode, KeyEvent},
|
use nu_plugin_textview::TextView;
|
||||||
ExecutableCommand,
|
|
||||||
};
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_plugin::{serve_plugin, Plugin};
|
|
||||||
use nu_protocol::{outln, CallInfo, Primitive, Signature, UntaggedValue, Value};
|
|
||||||
use nu_source::AnchorLocation;
|
|
||||||
|
|
||||||
use syntect::easy::HighlightLines;
|
|
||||||
use syntect::highlighting::{Style, ThemeSet};
|
|
||||||
use syntect::parsing::SyntaxSet;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
enum DrawCommand {
|
|
||||||
DrawString(Style, String),
|
|
||||||
NextLine,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextView;
|
|
||||||
|
|
||||||
impl TextView {
|
|
||||||
fn new() -> TextView {
|
|
||||||
TextView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for TextView {
|
|
||||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
|
||||||
Ok(Signature::build("textview").desc("Autoview of text data."))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) {
|
|
||||||
if !input.is_empty() {
|
|
||||||
view_text_value(&input[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paint_textview(
|
|
||||||
draw_commands: &[DrawCommand],
|
|
||||||
starting_row: usize,
|
|
||||||
use_color_buffer: bool,
|
|
||||||
) -> usize {
|
|
||||||
let size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
|
||||||
|
|
||||||
// render
|
|
||||||
let mut pos = 0;
|
|
||||||
let width = size.0 as usize;
|
|
||||||
let height = size.1 as usize - 1;
|
|
||||||
let mut frame_buffer = vec![];
|
|
||||||
|
|
||||||
for command in draw_commands {
|
|
||||||
match command {
|
|
||||||
DrawCommand::DrawString(style, string) => {
|
|
||||||
for chr in string.chars() {
|
|
||||||
if chr == '\t' {
|
|
||||||
for _ in 0..8 {
|
|
||||||
frame_buffer.push((
|
|
||||||
' ',
|
|
||||||
style.foreground.r,
|
|
||||||
style.foreground.g,
|
|
||||||
style.foreground.b,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
pos += 8;
|
|
||||||
} else {
|
|
||||||
frame_buffer.push((
|
|
||||||
chr,
|
|
||||||
style.foreground.r,
|
|
||||||
style.foreground.g,
|
|
||||||
style.foreground.b,
|
|
||||||
));
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DrawCommand::NextLine => {
|
|
||||||
for _ in 0..(width - pos % width) {
|
|
||||||
frame_buffer.push((' ', 0, 0, 0));
|
|
||||||
}
|
|
||||||
pos += width - pos % width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_frame_buffer_rows = frame_buffer.len() / width;
|
|
||||||
let buffer_needs_scrolling = num_frame_buffer_rows > height;
|
|
||||||
|
|
||||||
// display
|
|
||||||
let mut ansi_strings = vec![];
|
|
||||||
let mut normal_chars = vec![];
|
|
||||||
|
|
||||||
for c in
|
|
||||||
&frame_buffer[starting_row * width..std::cmp::min(pos, (starting_row + height) * width)]
|
|
||||||
{
|
|
||||||
if use_color_buffer {
|
|
||||||
ansi_strings.push(ansi_term::Colour::RGB(c.1, c.2, c.3).paint(format!("{}", c.0)));
|
|
||||||
} else {
|
|
||||||
normal_chars.push(c.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer_needs_scrolling {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if use_color_buffer {
|
|
||||||
print!("{}", ansi_term::ANSIStrings(&ansi_strings));
|
|
||||||
} else {
|
|
||||||
let s: String = normal_chars.into_iter().collect();
|
|
||||||
print!("{}", s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer_needs_scrolling {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::MoveTo(0, size.1));
|
|
||||||
print!(
|
|
||||||
"{}",
|
|
||||||
ansi_term::Colour::Blue.paint("[ESC to quit, arrow keys to move]")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = std::io::stdout().flush();
|
|
||||||
|
|
||||||
num_frame_buffer_rows
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_view_lines_if_needed(draw_commands: Vec<DrawCommand>, use_color_buffer: bool) {
|
|
||||||
let mut starting_row = 0;
|
|
||||||
|
|
||||||
if let Ok(_raw) = crossterm::terminal::enable_raw_mode() {
|
|
||||||
let mut size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
|
||||||
let height = size.1 as usize - 1;
|
|
||||||
|
|
||||||
let mut max_bottom_line = paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
|
|
||||||
// Only scroll if needed
|
|
||||||
if max_bottom_line > height as usize {
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::Hide);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Ok(ev) = crossterm::event::read() {
|
|
||||||
if let crossterm::event::Event::Key(KeyEvent { code, modifiers }) = ev {
|
|
||||||
match code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
if starting_row > 0 {
|
|
||||||
starting_row -= 1;
|
|
||||||
max_bottom_line = paint_textview(
|
|
||||||
&draw_commands,
|
|
||||||
starting_row,
|
|
||||||
use_color_buffer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += 1;
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::Char('b')
|
|
||||||
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
|
||||||
{
|
|
||||||
starting_row -= std::cmp::min(height, starting_row);
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::PageUp => {
|
|
||||||
starting_row -= std::cmp::min(height, starting_row);
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::Char('f')
|
|
||||||
if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) =>
|
|
||||||
{
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += height;
|
|
||||||
|
|
||||||
if starting_row > (max_bottom_line - height) {
|
|
||||||
starting_row = max_bottom_line - height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
KeyCode::PageDown | KeyCode::Char(' ') => {
|
|
||||||
if starting_row < (max_bottom_line - height) {
|
|
||||||
starting_row += height;
|
|
||||||
|
|
||||||
if starting_row > (max_bottom_line - height) {
|
|
||||||
starting_row = max_bottom_line - height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(new_size) = crossterm::terminal::size() {
|
|
||||||
if size != new_size {
|
|
||||||
size = new_size;
|
|
||||||
let _ = std::io::stdout().execute(crossterm::terminal::Clear(
|
|
||||||
crossterm::terminal::ClearType::All,
|
|
||||||
));
|
|
||||||
max_bottom_line =
|
|
||||||
paint_textview(&draw_commands, starting_row, use_color_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = std::io::stdout().execute(crossterm::cursor::Show);
|
|
||||||
|
|
||||||
let _ = crossterm::terminal::disable_raw_mode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outln!("");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_view(s: &str) {
|
|
||||||
let mut v = vec![];
|
|
||||||
for line in s.lines() {
|
|
||||||
v.push(DrawCommand::DrawString(Style::default(), line.to_string()));
|
|
||||||
v.push(DrawCommand::NextLine);
|
|
||||||
}
|
|
||||||
scroll_view_lines_if_needed(v, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_text_value(value: &Value) {
|
|
||||||
let value_anchor = value.anchor();
|
|
||||||
if let UntaggedValue::Primitive(Primitive::String(ref s)) = &value.value {
|
|
||||||
if let Some(source) = value_anchor {
|
|
||||||
let extension: Option<String> = match source {
|
|
||||||
AnchorLocation::File(file) => {
|
|
||||||
let path = Path::new(&file);
|
|
||||||
path.extension().map(|x| x.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
AnchorLocation::Url(url) => {
|
|
||||||
let url = url::Url::parse(&url);
|
|
||||||
if let Ok(url) = url {
|
|
||||||
if let Some(mut segments) = url.path_segments() {
|
|
||||||
if let Some(file) = segments.next_back() {
|
|
||||||
let path = Path::new(file);
|
|
||||||
path.extension().map(|x| x.to_string_lossy().to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//FIXME: this probably isn't correct
|
|
||||||
AnchorLocation::Source(_source) => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
match extension {
|
|
||||||
Some(extension) => {
|
|
||||||
// Load these once at the start of your program
|
|
||||||
let ps: SyntaxSet =
|
|
||||||
syntect::dumps::from_binary(include_bytes!("../../assets/syntaxes.bin"));
|
|
||||||
|
|
||||||
if let Some(syntax) = ps.find_syntax_by_extension(&extension) {
|
|
||||||
let ts: ThemeSet =
|
|
||||||
syntect::dumps::from_binary(include_bytes!("../../assets/themes.bin"));
|
|
||||||
let mut h = HighlightLines::new(syntax, &ts.themes["OneHalfDark"]);
|
|
||||||
|
|
||||||
let mut v = vec![];
|
|
||||||
for line in s.lines() {
|
|
||||||
let ranges: Vec<(Style, &str)> = h.highlight(line, &ps);
|
|
||||||
|
|
||||||
for range in ranges {
|
|
||||||
v.push(DrawCommand::DrawString(range.0, range.1.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
v.push(DrawCommand::NextLine);
|
|
||||||
}
|
|
||||||
scroll_view_lines_if_needed(v, true);
|
|
||||||
} else {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scroll_view(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
serve_plugin(&mut TextView::new());
|
serve_plugin(&mut TextView::new());
|
||||||
|
|
Loading…
Reference in a new issue