Default plugins are independent and called from Nu. (#1322)

This commit is contained in:
Andrés N. Robalino 2020-01-31 17:45:33 -05:00 committed by GitHub
parent 4e201d20ca
commit 3610baa227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 805 additions and 1511 deletions

View File

@ -123,9 +123,9 @@ futures-util = "0.3.1"
termcolor = "1.1.0"
natural = "0.3.0"
parking_lot = "0.10.0"
futures-timer = "1.0.2"
meval = "0.2"
futures-timer = { version = "1.0.2", optional = true }
clipboard = {version = "0.5", optional = true }
ptree = {version = "0.2" }
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"]
# Default
sys = ["heim", "battery"]
ps = ["heim"]
textview = ["crossterm", "syntect", "onig_sys", "url"]
inc = ["nu_plugin_inc"]
textview = ["crossterm", "syntect", "onig_sys", "url", "nu_plugin_textview"]
sys = ["heim", "battery", "nu_plugin_sys"]
ps = ["heim", "futures-timer", "nu_plugin_ps"]
inc = ["semver", "nu_plugin_inc"]
str = ["nu_plugin_str"]
# Stable

View File

@ -862,7 +862,7 @@ pub fn delimited_brace(input: NomSpan) -> IResult<NomSpan, SpannedToken> {
Ok((
input,
TokenTreeBuilder::spanned_square(tokens.item, (left, right), tokens.span),
TokenTreeBuilder::spanned_brace(tokens.item, (left, right), tokens.span),
))
}

View File

@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
nu-source = { path = "../nu-source", version = "0.9.0" }
nu-errors = { path = "../nu-errors", version = "0.9.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
semver = "0.9.0"
[build-dependencies]

View File

@ -1,5 +1,5 @@
mod inc;
mod nu_plugin_inc;
mod nu;
pub use inc::Inc;

View File

@ -6,6 +6,9 @@ edition = "2018"
description = "A process list plugin for Nushell"
license = "MIT"
[lib]
doctest = false
[dependencies]
nu-plugin = { path = "../nu-plugin", version = "0.9.0" }
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }

View File

@ -0,0 +1,4 @@
mod nu;
mod ps;
pub use ps::Ps;

View File

@ -1,94 +1,6 @@
use futures::executor::block_on;
//use futures::stream::TryStreamExt;
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![])
}
}
use nu_plugin::serve_plugin;
use nu_plugin_ps::Ps;
fn main() {
serve_plugin(&mut Ps::new());
serve_plugin(&mut Ps::new())
}

View 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![])
}
}

View 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
}

View File

@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
nu-source = { path = "../nu-source", version = "0.9.0" }
nu-errors = { path = "../nu-errors", version = "0.9.0" }
nu-value-ext = { path = "../nu-value-ext", version = "0.9.0" }
regex = "1"
num-bigint = "0.2.3"

View File

@ -1,4 +1,4 @@
mod nu_plugin_str;
mod nu;
mod strutils;
pub use strutils::Str;

View File

@ -11,6 +11,7 @@ nu-plugin = { path = "../nu-plugin", version = "0.9.0" }
nu-protocol = { path = "../nu-protocol", version = "0.9.0" }
nu-source = { path = "../nu-source", version = "0.9.0" }
nu-errors = { path = "../nu-errors", version = "0.9.0" }
heim = "0.0.9"
futures-preview = { version = "=0.3.0-alpha.19", features = ["compat", "io-compat"] }
battery = "0.7.5"

View File

@ -0,0 +1,4 @@
mod nu;
mod sys;
pub use sys::Sys;

View File

@ -1,354 +1,5 @@
use std::ffi::OsStr;
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![])
}
}
use nu_plugin::serve_plugin;
use nu_plugin_sys::Sys;
fn main() {
serve_plugin(&mut Sys::new());

View 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![])
}
}

View 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()]
}

View File

@ -0,0 +1,4 @@
mod nu;
mod textview;
pub use textview::TextView;

View File

@ -1,304 +1,5 @@
use crossterm::{
event::{KeyCode, KeyEvent},
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);
}
}
}
use nu_plugin::serve_plugin;
use nu_plugin_textview::TextView;
fn main() {
serve_plugin(&mut TextView::new());

View 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]);
}
}
}

View 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);
}
}
}

View File

@ -1,93 +1,5 @@
use futures::executor::block_on;
//use futures::stream::TryStreamExt;
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![])
}
}
use nu_plugin::serve_plugin;
use nu_plugin_ps::Ps;
fn main() {
serve_plugin(&mut Ps::new());

View File

@ -1,354 +1,5 @@
use std::ffi::OsStr;
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![])
}
}
use nu_plugin::serve_plugin;
use nu_plugin_sys::Sys;
fn main() {
serve_plugin(&mut Sys::new());

View File

@ -1,304 +1,5 @@
use crossterm::{
event::{KeyCode, KeyEvent},
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);
}
}
}
use nu_plugin::serve_plugin;
use nu_plugin_textview::TextView;
fn main() {
serve_plugin(&mut TextView::new());