forked from extern/nushell
Move old plugins (#4721)
This commit is contained in:
21
crates/old/nu_plugin_chart/Cargo.toml
Normal file
21
crates/old/nu_plugin_chart/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A plugin to display charts"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_chart"
|
||||
version = "0.59.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = { path="../nu-data", version = "0.59.1" }
|
||||
nu-errors = { path="../nu-errors", version = "0.59.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.59.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.59.1" }
|
||||
nu-source = { path="../nu-source", version = "0.59.1" }
|
||||
nu-value-ext = { path="../nu-value-ext", version = "0.59.1" }
|
||||
|
||||
crossterm = "0.19.0"
|
||||
tui = { version="0.15.0", default-features=false, features=["crossterm"] }
|
119
crates/old/nu_plugin_chart/src/bar.rs
Normal file
119
crates/old/nu_plugin_chart/src/bar.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use nu_data::utils::Model;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::BarChart,
|
||||
};
|
||||
|
||||
const DEFAULT_COLOR: Color = Color::Green;
|
||||
|
||||
pub struct Bar<'a> {
|
||||
pub title: &'a str,
|
||||
pub data: Vec<(&'a str, u64)>,
|
||||
pub enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
impl<'a> Bar<'a> {
|
||||
pub fn from_model(model: &'a Model) -> Result<Bar<'a>, ShellError> {
|
||||
let mut data = Vec::new();
|
||||
let mut data_points = Vec::new();
|
||||
|
||||
for percentages in model
|
||||
.percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let mut percentages_collected = vec![];
|
||||
|
||||
for percentage in percentages.table_entries().cloned().collect::<Vec<_>>() {
|
||||
percentages_collected.push(percentage.as_u64()?);
|
||||
}
|
||||
|
||||
data_points.push(percentages_collected);
|
||||
}
|
||||
|
||||
let mark_in = if model.labels.y.len() <= 1 {
|
||||
0
|
||||
} else {
|
||||
(model.labels.y.len() as f64 / 2.0).floor() as usize
|
||||
};
|
||||
|
||||
for idx in 0..model.labels.x.len() {
|
||||
let mut current = 0;
|
||||
|
||||
loop {
|
||||
let label = if current == mark_in {
|
||||
model
|
||||
.labels
|
||||
.at(idx)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let percentages_collected = data_points
|
||||
.get(current)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?;
|
||||
|
||||
data.push((
|
||||
label,
|
||||
*percentages_collected
|
||||
.get(idx)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?,
|
||||
));
|
||||
|
||||
current += 1;
|
||||
|
||||
if current == model.labels.y.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Bar {
|
||||
title: "Bar Chart",
|
||||
data: (&data[..]).to_vec(),
|
||||
enhanced_graphics: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw<T>(&mut self, ui: &mut tui::Terminal<T>) -> std::io::Result<()>
|
||||
where
|
||||
T: tui::backend::Backend,
|
||||
{
|
||||
ui.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(0)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
let barchart = BarChart::default()
|
||||
.data(&self.data)
|
||||
.bar_width(9)
|
||||
.bar_style(Style::default().fg(DEFAULT_COLOR))
|
||||
.value_style(
|
||||
Style::default()
|
||||
.bg(Color::Black)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
f.render_widget(barchart, chunks[0]);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_right(&mut self) {
|
||||
let one_bar = self.data.remove(0);
|
||||
self.data.push(one_bar);
|
||||
}
|
||||
|
||||
pub fn on_left(&mut self) {
|
||||
if let Some(one_bar) = self.data.pop() {
|
||||
self.data.insert(0, one_bar);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::ChartBar;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ChartBar::new());
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::ChartLine;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ChartLine::new());
|
||||
}
|
5
crates/old/nu_plugin_chart/src/lib.rs
Normal file
5
crates/old/nu_plugin_chart/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod bar;
|
||||
mod line;
|
||||
mod nu;
|
||||
|
||||
pub use nu::{ChartBar, ChartLine};
|
144
crates/old/nu_plugin_chart/src/line.rs
Normal file
144
crates/old/nu_plugin_chart/src/line.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use nu_data::utils::Model;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::Span,
|
||||
widgets::{Axis, Chart, Dataset, GraphType},
|
||||
};
|
||||
|
||||
const DEFAULT_COLOR: Color = Color::Green;
|
||||
|
||||
const DEFAULT_LINE_COLORS: [Color; 5] = [
|
||||
Color::Green,
|
||||
Color::Cyan,
|
||||
Color::Magenta,
|
||||
Color::Yellow,
|
||||
Color::Red,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Line {
|
||||
x_labels: Vec<String>,
|
||||
x_range: [f64; 2],
|
||||
y_range: [f64; 2],
|
||||
datasets_names: Vec<String>,
|
||||
data: Vec<Vec<(f64, f64)>>,
|
||||
}
|
||||
|
||||
impl<'a> Line {
|
||||
pub fn from_model(model: &'a Model) -> Result<Line, ShellError> {
|
||||
Ok(Line {
|
||||
x_labels: model.labels.x.to_vec(),
|
||||
x_range: [
|
||||
model.ranges.0.start.as_u64()? as f64,
|
||||
model.labels.x.len() as f64,
|
||||
],
|
||||
y_range: [
|
||||
model.ranges.1.start.as_u64()? as f64,
|
||||
model.ranges.1.end.as_u64()? as f64,
|
||||
],
|
||||
datasets_names: if model.labels.y.len() == 1 {
|
||||
vec!["".to_string()]
|
||||
} else {
|
||||
model.labels.y.to_vec()
|
||||
},
|
||||
data: model
|
||||
.data
|
||||
.table_entries()
|
||||
.collect::<Vec<_>>()
|
||||
.iter()
|
||||
.map(|subset| {
|
||||
subset
|
||||
.table_entries()
|
||||
.enumerate()
|
||||
.map(|(idx, data_point)| {
|
||||
(
|
||||
idx as f64,
|
||||
if let Ok(point) = data_point.as_u64() {
|
||||
point as f64
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw<T>(&mut self, ui: &mut tui::Terminal<T>) -> std::io::Result<()>
|
||||
where
|
||||
T: tui::backend::Backend,
|
||||
{
|
||||
ui.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
let x_labels = self
|
||||
.x_labels
|
||||
.iter()
|
||||
.map(move |label| {
|
||||
Span::styled(label, Style::default().add_modifier(Modifier::BOLD))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let y_labels = vec![
|
||||
Span::styled(
|
||||
self.y_range[0].to_string(),
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(((self.y_range[0] + self.y_range[1]) / 2.0).to_string()),
|
||||
Span::styled(
|
||||
self.y_range[1].to_string(),
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
),
|
||||
];
|
||||
|
||||
let marker = if x_labels.len() > 60 {
|
||||
symbols::Marker::Braille
|
||||
} else {
|
||||
symbols::Marker::Dot
|
||||
};
|
||||
|
||||
let datasets = self
|
||||
.data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, data_series)| {
|
||||
Dataset::default()
|
||||
.name(&self.datasets_names[idx])
|
||||
.marker(marker)
|
||||
.graph_type(GraphType::Line)
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(*DEFAULT_LINE_COLORS.get(idx).unwrap_or(&DEFAULT_COLOR)),
|
||||
)
|
||||
.data(data_series)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let chart = Chart::new(datasets)
|
||||
.x_axis(
|
||||
Axis::default()
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.labels(x_labels)
|
||||
.bounds(self.x_range),
|
||||
)
|
||||
.y_axis(
|
||||
Axis::default()
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.labels(y_labels)
|
||||
.bounds(self.y_range),
|
||||
);
|
||||
f.render_widget(chart, chunks[0]);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
5
crates/old/nu_plugin_chart/src/nu.rs
Normal file
5
crates/old/nu_plugin_chart/src/nu.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod bar;
|
||||
mod line;
|
||||
|
||||
pub use bar::SubCommand as ChartBar;
|
||||
pub use line::SubCommand as ChartLine;
|
371
crates/old/nu_plugin_chart/src/nu/bar.rs
Normal file
371
crates/old/nu_plugin_chart/src/nu/bar.rs
Normal file
@ -0,0 +1,371 @@
|
||||
use nu_data::utils::{report as build_report, Model};
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{Tagged, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use crate::bar::Bar;
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io::stdout,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
}
|
||||
|
||||
pub enum Columns {
|
||||
One(Tagged<String>),
|
||||
Two(Tagged<String>, Tagged<String>),
|
||||
None,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct SubCommand {
|
||||
pub reduction: nu_data::utils::Reduction,
|
||||
pub columns: Columns,
|
||||
pub eval: Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
pub format: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for SubCommand {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand {
|
||||
pub fn new() -> SubCommand {
|
||||
SubCommand {
|
||||
reduction: nu_data::utils::Reduction::Count,
|
||||
columns: Columns::None,
|
||||
eval: None,
|
||||
format: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display(model: &Model) -> Result<(), Box<dyn Error>> {
|
||||
let mut app = Bar::from_model(model)?;
|
||||
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
if event::poll(tick_rate - last_tick.elapsed()).is_ok() {
|
||||
if let Ok(CEvent::Key(key)) = event::read() {
|
||||
let _ = tx.send(Event::Input(key));
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
let _ = tx.send(Event::Tick);
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
terminal.clear()?;
|
||||
|
||||
loop {
|
||||
app.draw(&mut terminal)?;
|
||||
|
||||
match rx.recv()? {
|
||||
Event::Input(event) => match event.code {
|
||||
KeyCode::Left => app.on_left(),
|
||||
KeyCode::Right => app.on_right(),
|
||||
KeyCode::Char('q') => {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
break;
|
||||
}
|
||||
},
|
||||
Event::Tick => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Plugin for SubCommand {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("chart bar")
|
||||
.desc("Bar charts")
|
||||
.switch("acc", "accumulate values", Some('a'))
|
||||
.optional(
|
||||
"columns",
|
||||
SyntaxShape::Any,
|
||||
"the columns to chart [x-axis y-axis]",
|
||||
)
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"column to use for evaluation",
|
||||
Some('u'),
|
||||
)
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
))
|
||||
}
|
||||
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(true)),
|
||||
..
|
||||
}) = call_info.args.get("acc")
|
||||
{
|
||||
self.reduction = nu_data::utils::Reduction::Accumulate;
|
||||
}
|
||||
|
||||
let _ = self.run(call_info, input);
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand {
|
||||
fn run(&mut self, call_info: CallInfo, input: Vec<Value>) -> Result<(), ShellError> {
|
||||
let args = call_info.args;
|
||||
let name = call_info.name_tag;
|
||||
|
||||
self.eval = if let Some(path) = args.get("use") {
|
||||
Some(evaluator(path.as_column_path()?.item))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.format = if let Some(fmt) = args.get("format") {
|
||||
Some(fmt.as_string()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for arg in args.positional_iter() {
|
||||
match arg {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(column)),
|
||||
tag,
|
||||
} => {
|
||||
let column = column.clone();
|
||||
self.columns = Columns::One(column.tagged(tag));
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Table(arguments),
|
||||
tag,
|
||||
} => {
|
||||
if arguments.len() > 1 {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
let col2 = arguments
|
||||
.get(1)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::Two(col1, col2);
|
||||
} else {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::One(col1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let data = UntaggedValue::table(&input).into_value(&name);
|
||||
|
||||
match &self.columns {
|
||||
Columns::Two(col1, col2) => {
|
||||
let key = col1.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let key = col2.clone();
|
||||
let splitter = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => nu_value_ext::as_string(&key),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: Some(splitter),
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&build_report(&data, options, &name)?);
|
||||
}
|
||||
Columns::One(col) => {
|
||||
let key = col.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: None,
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&build_report(&data, options, &name)?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
369
crates/old/nu_plugin_chart/src/nu/line.rs
Normal file
369
crates/old/nu_plugin_chart/src/nu/line.rs
Normal file
@ -0,0 +1,369 @@
|
||||
use nu_data::utils::{report as build_report, Model};
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{Tagged, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use crate::line::Line;
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io::stdout,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
}
|
||||
|
||||
pub enum Columns {
|
||||
One(Tagged<String>),
|
||||
Two(Tagged<String>, Tagged<String>),
|
||||
None,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct SubCommand {
|
||||
pub reduction: nu_data::utils::Reduction,
|
||||
pub columns: Columns,
|
||||
pub eval: Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
pub format: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for SubCommand {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand {
|
||||
pub fn new() -> SubCommand {
|
||||
SubCommand {
|
||||
reduction: nu_data::utils::Reduction::Count,
|
||||
columns: Columns::None,
|
||||
eval: None,
|
||||
format: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display(model: &Model) -> Result<(), Box<dyn Error>> {
|
||||
let mut app = Line::from_model(model)?;
|
||||
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
if event::poll(tick_rate - last_tick.elapsed()).is_ok() {
|
||||
if let Ok(CEvent::Key(key)) = event::read() {
|
||||
let _ = tx.send(Event::Input(key));
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
let _ = tx.send(Event::Tick);
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
terminal.clear()?;
|
||||
|
||||
loop {
|
||||
app.draw(&mut terminal)?;
|
||||
|
||||
match rx.recv()? {
|
||||
Event::Input(event) => match event.code {
|
||||
KeyCode::Char('q') => {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
break;
|
||||
}
|
||||
},
|
||||
Event::Tick => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Plugin for SubCommand {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("chart line")
|
||||
.desc("Line charts")
|
||||
.switch("acc", "accumulate values", Some('a'))
|
||||
.optional(
|
||||
"columns",
|
||||
SyntaxShape::Any,
|
||||
"the columns to chart [x-axis y-axis]",
|
||||
)
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"column to use for evaluation",
|
||||
Some('u'),
|
||||
)
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
))
|
||||
}
|
||||
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(true)),
|
||||
..
|
||||
}) = call_info.args.get("acc")
|
||||
{
|
||||
self.reduction = nu_data::utils::Reduction::Accumulate;
|
||||
}
|
||||
|
||||
let _ = self.run(call_info, input);
|
||||
}
|
||||
}
|
||||
|
||||
impl SubCommand {
|
||||
fn run(&mut self, call_info: CallInfo, input: Vec<Value>) -> Result<(), ShellError> {
|
||||
let args = call_info.args;
|
||||
let name = call_info.name_tag;
|
||||
|
||||
self.eval = if let Some(path) = args.get("use") {
|
||||
Some(evaluator(path.as_column_path()?.item))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.format = if let Some(fmt) = args.get("format") {
|
||||
Some(fmt.as_string()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for arg in args.positional_iter() {
|
||||
match arg {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(column)),
|
||||
tag,
|
||||
} => {
|
||||
let column = column.clone();
|
||||
self.columns = Columns::One(column.tagged(tag));
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Table(arguments),
|
||||
tag,
|
||||
} => {
|
||||
if arguments.len() > 1 {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
let col2 = arguments
|
||||
.get(1)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::Two(col1, col2);
|
||||
} else {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::One(col1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let data = UntaggedValue::table(&input).into_value(&name);
|
||||
|
||||
match &self.columns {
|
||||
Columns::Two(col1, col2) => {
|
||||
let key = col1.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let key = col2.clone();
|
||||
let splitter = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => nu_value_ext::as_string(&key),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: Some(splitter),
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&build_report(&data, options, &name)?);
|
||||
}
|
||||
Columns::One(col) => {
|
||||
let key = col.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: None,
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&build_report(&data, options, &name)?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user