mirror of
https://github.com/nushell/nushell
synced 2025-01-13 21:55:07 +00:00
Delete unused files (#7668)
Just tidying up a bit, deleting the unused files in `crates/old`. The files can still be accessed in source control history if anyone needs them.
This commit is contained in:
parent
b17e9f4ed0
commit
d7af461173
56 changed files with 0 additions and 3111 deletions
|
@ -1,21 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A plugin to display charts"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_chart"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = { path="../nu-data", version = "0.73.1" }
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
nu-value-ext = { path="../nu-value-ext", version = "0.73.1" }
|
||||
|
||||
crossterm = "0.19.0"
|
||||
tui = { version="0.15.0", default-features=false, features=["crossterm"] }
|
|
@ -1,119 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::ChartBar;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ChartBar::new());
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::ChartLine;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ChartLine::new());
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod bar;
|
||||
mod line;
|
||||
mod nu;
|
||||
|
||||
pub use nu::{ChartBar, ChartLine};
|
|
@ -1,144 +0,0 @@
|
|||
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(())
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod bar;
|
||||
mod line;
|
||||
|
||||
pub use bar::SubCommand as ChartBar;
|
||||
pub use line::SubCommand as ChartLine;
|
|
@ -1,371 +0,0 @@
|
|||
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")
|
||||
.usage("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),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,369 +0,0 @@
|
|||
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")
|
||||
.usage("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),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A converter plugin to the bson format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_from_bson"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] }
|
||||
bson = { version = "2.0.1", features = [ "chrono-0_4" ] }
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
|
||||
[build-dependencies]
|
|
@ -1,212 +0,0 @@
|
|||
use bigdecimal::BigDecimal;
|
||||
use bson::{spec::BinarySubtype, Bson};
|
||||
use nu_errors::{ExpectedRange, ShellError};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tag};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FromBson {
|
||||
pub state: Vec<u8>,
|
||||
pub name_tag: Tag,
|
||||
}
|
||||
|
||||
impl FromBson {
|
||||
pub fn new() -> FromBson {
|
||||
FromBson {
|
||||
state: vec![],
|
||||
name_tag: Tag::unknown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bson_array(input: &[Bson], tag: Tag) -> Result<Vec<Value>, ShellError> {
|
||||
let mut out = vec![];
|
||||
|
||||
for value in input {
|
||||
out.push(convert_bson_value_to_nu_value(value, &tag)?);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
Ok(match v {
|
||||
Bson::Double(n) => UntaggedValue::Primitive(Primitive::from(*n)).into_value(&tag),
|
||||
Bson::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
||||
}
|
||||
Bson::Array(a) => UntaggedValue::Table(bson_array(a, tag.clone())?).into_value(&tag),
|
||||
Bson::Document(doc) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
for (k, v) in doc {
|
||||
collected.insert_value(k.clone(), convert_bson_value_to_nu_value(v, &tag)?);
|
||||
}
|
||||
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::Boolean(b) => UntaggedValue::Primitive(Primitive::Boolean(*b)).into_value(&tag),
|
||||
Bson::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
Bson::RegularExpression(regx) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$regex".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(®x.pattern)))
|
||||
.into_value(&tag),
|
||||
);
|
||||
collected.insert_value(
|
||||
"$options".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(®x.options)))
|
||||
.into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::Int32(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
Bson::Int64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
Bson::Decimal128(n) => {
|
||||
// TODO: this really isn't great, and we should update this to do a higher
|
||||
// fidelity translation
|
||||
let decimal = BigDecimal::from_str(&n.to_string()).map_err(|_| {
|
||||
ShellError::range_error(
|
||||
ExpectedRange::BigDecimal,
|
||||
&n.spanned(span),
|
||||
"converting BSON Decimal128 to BigDecimal".to_owned(),
|
||||
)
|
||||
})?;
|
||||
UntaggedValue::Primitive(Primitive::Decimal(decimal)).into_value(&tag)
|
||||
}
|
||||
Bson::JavaScriptCode(js) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$javascript".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(js))).into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::JavaScriptCodeWithScope(js) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$javascript".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(&js.code)))
|
||||
.into_value(&tag),
|
||||
);
|
||||
collected.insert_value(
|
||||
"$scope".to_string(),
|
||||
convert_bson_value_to_nu_value(&Bson::Document(js.scope.to_owned()), tag)?,
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::Timestamp(ts) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$timestamp".to_string(),
|
||||
UntaggedValue::int(ts.time).into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::Binary(binary) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$binary_subtype".to_string(),
|
||||
match binary.subtype {
|
||||
BinarySubtype::UserDefined(u) => UntaggedValue::int(u),
|
||||
_ => UntaggedValue::Primitive(Primitive::String(binary_subtype_to_string(
|
||||
binary.subtype,
|
||||
))),
|
||||
}
|
||||
.into_value(&tag),
|
||||
);
|
||||
collected.insert_value(
|
||||
"$binary".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::Binary(binary.bytes.to_owned()))
|
||||
.into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::ObjectId(obj_id) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$object_id".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(obj_id.to_hex())).into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::DateTime(dt) => {
|
||||
UntaggedValue::Primitive(Primitive::Date(dt.to_chrono().into())).into_value(&tag)
|
||||
}
|
||||
Bson::Symbol(s) => {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
collected.insert_value(
|
||||
"$symbol".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag),
|
||||
);
|
||||
collected.into_value()
|
||||
}
|
||||
Bson::Undefined | Bson::MaxKey | Bson::MinKey | Bson::DbPointer(_) => {
|
||||
// TODO Impelmenting Bson::Undefined, Bson::MaxKey, Bson::MinKey and Bson::DbPointer
|
||||
// These Variants weren't present in the previous version.
|
||||
TaggedDictBuilder::new(tag).into_value()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn binary_subtype_to_string(bst: BinarySubtype) -> String {
|
||||
match bst {
|
||||
BinarySubtype::Generic => "generic",
|
||||
BinarySubtype::Function => "function",
|
||||
BinarySubtype::BinaryOld => "binary_old",
|
||||
BinarySubtype::UuidOld => "uuid_old",
|
||||
BinarySubtype::Uuid => "uuid",
|
||||
BinarySubtype::Md5 => "md5",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BytesReader {
|
||||
pos: usize,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BytesReader {
|
||||
fn new(bytes: Vec<u8>) -> BytesReader {
|
||||
BytesReader {
|
||||
pos: 0,
|
||||
inner: bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Read for BytesReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let src: &mut &[u8] = &mut self.inner[self.pos..].as_ref();
|
||||
let diff = src.read(buf)?;
|
||||
self.pos += diff;
|
||||
Ok(diff)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bson_bytes_to_value(bytes: Vec<u8>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let mut docs = Vec::new();
|
||||
let mut b_reader = BytesReader::new(bytes);
|
||||
while let Ok(v) = bson::de::from_reader(&mut b_reader) {
|
||||
docs.push(Bson::Document(v));
|
||||
}
|
||||
|
||||
convert_bson_value_to_nu_value(&Bson::Array(docs), tag)
|
||||
}
|
||||
|
||||
pub fn from_bson(bytes: Vec<u8>, name_tag: Tag) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match from_bson_bytes_to_value(bytes, name_tag.clone()) {
|
||||
Ok(x) => Ok(vec![ReturnSuccess::value(x)]),
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"Could not parse as BSON",
|
||||
"input cannot be parsed as BSON",
|
||||
name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mod from_bson;
|
||||
mod nu;
|
||||
|
||||
pub use from_bson::FromBson;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_from_bson::FromBson;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut FromBson::new())
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::FromBson;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
impl Plugin for FromBson {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("from bson")
|
||||
.usage("Convert from .bson binary into table")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.name_tag = call_info.name_tag;
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
self.state.extend_from_slice(&b);
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected binary from pipeline",
|
||||
"requires binary input",
|
||||
self.name_tag.clone(),
|
||||
"value originates from here",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
crate::from_bson::from_bson(self.state.clone(), Tag::unknown())
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod integration {}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A converter plugin to the mp4 format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_from_mp4"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
tempfile = "3.2.0"
|
||||
mp4 = "0.9.0"
|
||||
|
||||
[build-dependencies]
|
|
@ -1,174 +0,0 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, ReturnValue, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FromMp4 {
|
||||
pub state: Vec<u8>,
|
||||
pub name_tag: Tag,
|
||||
}
|
||||
|
||||
impl FromMp4 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: vec![],
|
||||
name_tag: Tag::unknown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_mp4_file_to_nu_value(path: &Path, tag: Tag) -> Result<Value, mp4::Error> {
|
||||
let mp4 = mp4::read_mp4(File::open(path).expect("Could not open mp4 file to read metadata"))?;
|
||||
|
||||
let mut dict = TaggedDictBuilder::new(tag.clone());
|
||||
|
||||
// Build tracks table
|
||||
let mut tracks = Vec::new();
|
||||
for track in mp4.tracks().values() {
|
||||
let mut curr_track_dict = TaggedDictBuilder::new(tag.clone());
|
||||
|
||||
curr_track_dict.insert_untagged("track id", UntaggedValue::int(track.track_id()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"track type",
|
||||
match track.track_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"media type",
|
||||
match track.media_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"box type",
|
||||
match track.box_type() {
|
||||
Ok(t) => UntaggedValue::string(t.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged("width", UntaggedValue::int(track.width()));
|
||||
curr_track_dict.insert_untagged("height", UntaggedValue::int(track.height()));
|
||||
curr_track_dict.insert_untagged("frame_rate", UntaggedValue::from(track.frame_rate()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"sample freq index",
|
||||
match track.sample_freq_index() {
|
||||
Ok(sfi) => UntaggedValue::string(sfi.freq().to_string()), // this is a string for formatting reasons
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"channel config",
|
||||
match track.channel_config() {
|
||||
Ok(cc) => UntaggedValue::string(cc.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged("language", UntaggedValue::string(track.language()));
|
||||
curr_track_dict.insert_untagged("timescale", UntaggedValue::int(track.timescale()));
|
||||
curr_track_dict.insert_untagged(
|
||||
"duration",
|
||||
UntaggedValue::duration(track.duration().as_nanos()),
|
||||
);
|
||||
curr_track_dict.insert_untagged("bitrate", UntaggedValue::int(track.bitrate()));
|
||||
curr_track_dict.insert_untagged("sample count", UntaggedValue::int(track.sample_count()));
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"video profile",
|
||||
match track.video_profile() {
|
||||
Ok(vp) => UntaggedValue::string(vp.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"audio profile",
|
||||
match track.audio_profile() {
|
||||
Ok(ap) => UntaggedValue::string(ap.to_string()),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"sequence parameter set",
|
||||
match track.sequence_parameter_set() {
|
||||
Ok(sps) => UntaggedValue::string(format!("{:X?}", sps)),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
curr_track_dict.insert_untagged(
|
||||
"picture parameter set",
|
||||
match track.picture_parameter_set() {
|
||||
Ok(pps) => UntaggedValue::string(format!("{:X?}", pps)),
|
||||
Err(_) => UntaggedValue::from("Unknown"),
|
||||
},
|
||||
);
|
||||
|
||||
// push curr track to tracks vec
|
||||
tracks.push(curr_track_dict.into_value());
|
||||
}
|
||||
|
||||
dict.insert_untagged("size", UntaggedValue::big_int(mp4.size()));
|
||||
|
||||
dict.insert_untagged(
|
||||
"major brand",
|
||||
UntaggedValue::string(mp4.major_brand().to_string()),
|
||||
);
|
||||
|
||||
dict.insert_untagged("minor version", UntaggedValue::int(mp4.minor_version()));
|
||||
|
||||
dict.insert_untagged(
|
||||
"compatible brands",
|
||||
UntaggedValue::string(format!("{:?}", mp4.compatible_brands())),
|
||||
);
|
||||
|
||||
dict.insert_untagged(
|
||||
"duration",
|
||||
UntaggedValue::duration(mp4.duration().as_nanos()),
|
||||
);
|
||||
|
||||
dict.insert_untagged("timescale", UntaggedValue::int(mp4.timescale()));
|
||||
dict.insert_untagged("is fragmented", UntaggedValue::boolean(mp4.is_fragmented()));
|
||||
dict.insert_untagged("tracks", UntaggedValue::Table(tracks).into_value(&tag));
|
||||
|
||||
Ok(dict.into_value())
|
||||
}
|
||||
|
||||
pub fn from_mp4_bytes_to_value(mut bytes: Vec<u8>, tag: Tag) -> Result<Value, std::io::Error> {
|
||||
let mut tempfile = tempfile::NamedTempFile::new()?;
|
||||
tempfile.write_all(bytes.as_mut_slice())?;
|
||||
match convert_mp4_file_to_nu_value(tempfile.path(), tag) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_mp4(bytes: Vec<u8>, name_tag: Tag) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match from_mp4_bytes_to_value(bytes, name_tag.clone()) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(list.into_iter().map(ReturnSuccess::value).collect()),
|
||||
_ => Ok(vec![ReturnSuccess::value(x)]),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"Could not parse as MP4",
|
||||
"input cannot be parsed as MP4",
|
||||
&name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mod from_mp4;
|
||||
mod nu;
|
||||
|
||||
pub use from_mp4::FromMp4;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_from_mp4::FromMp4;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut FromMp4::new())
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::FromMp4;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Primitive, ReturnValue, Signature, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
impl Plugin for FromMp4 {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("from mp4")
|
||||
.usage("Get meta-data of mp4 file")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.name_tag = call_info.name_tag;
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
self.state.extend_from_slice(&b);
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected binary from pipeline",
|
||||
"requires binary input",
|
||||
self.name_tag.clone(),
|
||||
"value originates from here",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
crate::from_mp4::from_mp4(self.state.clone(), Tag::unknown())
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod integration {}
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A converter plugin to the bson format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_from_sqlite"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] }
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
version = "0.26.1"
|
||||
|
||||
[build-dependencies]
|
|
@ -1,138 +0,0 @@
|
|||
use bigdecimal::FromPrimitive;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use rusqlite::{types::ValueRef, Connection, Row};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FromSqlite {
|
||||
pub state: Vec<u8>,
|
||||
pub name_tag: Tag,
|
||||
pub tables: Vec<String>,
|
||||
}
|
||||
|
||||
impl FromSqlite {
|
||||
pub fn new() -> FromSqlite {
|
||||
FromSqlite {
|
||||
state: vec![],
|
||||
name_tag: Tag::unknown(),
|
||||
tables: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_sqlite_file_to_nu_value(
|
||||
path: &Path,
|
||||
tag: impl Into<Tag> + Clone,
|
||||
tables: Vec<String>,
|
||||
) -> Result<Value, rusqlite::Error> {
|
||||
let conn = Connection::open(path)?;
|
||||
|
||||
let mut meta_out = Vec::new();
|
||||
let mut meta_stmt = conn.prepare("select name from sqlite_master where type='table'")?;
|
||||
let mut meta_rows = meta_stmt.query([])?;
|
||||
|
||||
while let Some(meta_row) = meta_rows.next()? {
|
||||
let table_name: String = meta_row.get(0)?;
|
||||
if tables.is_empty() || tables.contains(&table_name) {
|
||||
let mut meta_dict = TaggedDictBuilder::new(tag.clone());
|
||||
let mut out = Vec::new();
|
||||
let mut table_stmt = conn.prepare(&format!("select * from [{}]", table_name))?;
|
||||
let mut table_rows = table_stmt.query([])?;
|
||||
while let Some(table_row) = table_rows.next()? {
|
||||
out.push(convert_sqlite_row_to_nu_value(table_row, tag.clone()))
|
||||
}
|
||||
meta_dict.insert_value(
|
||||
"table_name".to_string(),
|
||||
UntaggedValue::Primitive(Primitive::String(table_name)).into_value(tag.clone()),
|
||||
);
|
||||
meta_dict.insert_value(
|
||||
"table_values",
|
||||
UntaggedValue::Table(out).into_value(tag.clone()),
|
||||
);
|
||||
meta_out.push(meta_dict.into_value());
|
||||
}
|
||||
}
|
||||
let tag = tag.into();
|
||||
Ok(UntaggedValue::Table(meta_out).into_value(tag))
|
||||
}
|
||||
|
||||
fn convert_sqlite_row_to_nu_value(row: &Row, tag: impl Into<Tag> + Clone) -> Value {
|
||||
let mut collected = TaggedDictBuilder::new(tag.clone());
|
||||
for (i, c) in row.as_ref().column_names().iter().enumerate() {
|
||||
collected.insert_value(
|
||||
c.to_string(),
|
||||
convert_sqlite_value_to_nu_value(row.get_ref_unwrap(i), tag.clone()),
|
||||
);
|
||||
}
|
||||
collected.into_value()
|
||||
}
|
||||
|
||||
fn convert_sqlite_value_to_nu_value(value: ValueRef, tag: impl Into<Tag> + Clone) -> Value {
|
||||
match value {
|
||||
ValueRef::Null => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(""))).into_value(tag)
|
||||
}
|
||||
ValueRef::Integer(i) => UntaggedValue::int(i).into_value(tag),
|
||||
ValueRef::Real(f) => {
|
||||
let f = bigdecimal::BigDecimal::from_f64(f);
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
match f {
|
||||
Some(d) => UntaggedValue::decimal(d).into_value(tag),
|
||||
None => UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can not convert f64 to big decimal",
|
||||
"can not convert to decimal",
|
||||
span,
|
||||
))
|
||||
.into_value(tag),
|
||||
}
|
||||
}
|
||||
ValueRef::Text(s) => {
|
||||
// this unwrap is safe because we know the ValueRef is Text.
|
||||
UntaggedValue::Primitive(Primitive::String(String::from_utf8_lossy(s).to_string()))
|
||||
.into_value(tag)
|
||||
}
|
||||
ValueRef::Blob(u) => UntaggedValue::binary(u.to_owned()).into_value(tag),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_sqlite_bytes_to_value(
|
||||
mut bytes: Vec<u8>,
|
||||
tag: impl Into<Tag> + Clone,
|
||||
tables: Vec<String>,
|
||||
) -> Result<Value, std::io::Error> {
|
||||
// FIXME: should probably write a sqlite virtual filesystem
|
||||
// that will allow us to use bytes as a file to avoid this
|
||||
// write out, but this will require C code. Might be
|
||||
// best done as a PR to rusqlite.
|
||||
let mut tempfile = tempfile::NamedTempFile::new()?;
|
||||
tempfile.write_all(bytes.as_mut_slice())?;
|
||||
match convert_sqlite_file_to_nu_value(tempfile.path(), tag, tables) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_sqlite(
|
||||
bytes: Vec<u8>,
|
||||
name_tag: Tag,
|
||||
tables: Vec<String>,
|
||||
) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match from_sqlite_bytes_to_value(bytes, name_tag.clone(), tables) {
|
||||
Ok(x) => match x {
|
||||
Value {
|
||||
value: UntaggedValue::Table(list),
|
||||
..
|
||||
} => Ok(list.into_iter().map(ReturnSuccess::value).collect()),
|
||||
_ => Ok(vec![ReturnSuccess::value(x)]),
|
||||
},
|
||||
Err(_) => Err(ShellError::labeled_error(
|
||||
"Could not parse as SQLite",
|
||||
"input cannot be parsed as SQLite",
|
||||
&name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mod from_sqlite;
|
||||
mod nu;
|
||||
|
||||
pub use from_sqlite::FromSqlite;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_from_sqlite::FromSqlite;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut FromSqlite::new())
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::FromSqlite;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Primitive, ReturnValue, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
// Adapted from crates/nu-command/src/commands/dataframe/utils.rs
|
||||
fn convert_columns(columns: &[Value]) -> Result<Vec<String>, ShellError> {
|
||||
let res = columns
|
||||
.iter()
|
||||
.map(|value| match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => Ok(s.clone()),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Incorrect column format",
|
||||
"Only string as column name",
|
||||
&value.tag,
|
||||
)),
|
||||
})
|
||||
.collect::<Result<Vec<String>, _>>()?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
impl Plugin for FromSqlite {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("from sqlite")
|
||||
.named(
|
||||
"tables",
|
||||
SyntaxShape::Table,
|
||||
"Only convert specified tables",
|
||||
Some('t'),
|
||||
)
|
||||
.usage("Convert from sqlite binary into table")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.name_tag = call_info.name_tag;
|
||||
|
||||
if let Some(t) = call_info.args.get("tables") {
|
||||
if let UntaggedValue::Table(columns) = t.value.clone() {
|
||||
self.tables = convert_columns(columns.as_slice())?;
|
||||
}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match input {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Binary(b)),
|
||||
..
|
||||
} => {
|
||||
self.state.extend_from_slice(&b);
|
||||
}
|
||||
Value { tag, .. } => {
|
||||
return Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected binary from pipeline",
|
||||
"requires binary input",
|
||||
self.name_tag.clone(),
|
||||
"value originates from here",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
crate::from_sqlite::from_sqlite(self.state.clone(), Tag::unknown(), self.tables.clone())
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod integration {}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "An S3 plugin for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_s3"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
futures = { version="0.3.12", features=["compat", "io-compat"] }
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
s3handler = "0.7.5"
|
||||
|
||||
[build-dependencies]
|
|
@ -1,9 +0,0 @@
|
|||
Nu Plugin S3
|
||||
---
|
||||
|
||||
An S3 plugin for nu shell, it can load the content of S3 objects and convert into table
|
||||
|
||||
#### Snapshot
|
||||
In following example, the return content from httpbin is saved before as an object in AWS S3.
|
||||
![snapshot](https://raw.githubusercontent.com/yanganto/nu_plugin_s3/master/demo.png)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 79 KiB |
|
@ -1,134 +0,0 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, CommandAction, ReturnSuccess, ReturnValue, UntaggedValue, Value};
|
||||
use nu_source::{AnchorLocation, Tag};
|
||||
use s3handler::{CredentialConfig, Handler as S3Handler};
|
||||
|
||||
pub struct Handler {
|
||||
pub resource: Option<Value>,
|
||||
pub tag: Tag,
|
||||
pub has_raw: bool,
|
||||
pub config: CredentialConfig,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new() -> Handler {
|
||||
Handler {
|
||||
tag: Tag::unknown(),
|
||||
config: CredentialConfig {
|
||||
host: String::new(),
|
||||
access_key: String::new(),
|
||||
secret_key: String::new(),
|
||||
user: None,
|
||||
region: None,
|
||||
s3_type: None,
|
||||
secure: None,
|
||||
},
|
||||
resource: None,
|
||||
has_raw: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(&mut self, call_info: CallInfo) -> ReturnValue {
|
||||
self.resource = {
|
||||
let r = call_info.args.nth(0).ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"No obj or directory specified",
|
||||
"for command",
|
||||
&call_info.name_tag,
|
||||
)
|
||||
})?;
|
||||
Some(r.clone())
|
||||
};
|
||||
self.tag = call_info.name_tag.clone();
|
||||
self.has_raw = call_info.args.has("raw");
|
||||
|
||||
if let Some(e) = call_info.args.get("endpoint") {
|
||||
self.config.host = e.as_string()?
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No endpoint provided",
|
||||
"for command",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(access_key) = call_info.args.get("access-key") {
|
||||
self.config.access_key = access_key.as_string()?
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No access key provided",
|
||||
"for command",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(secret_key) = call_info.args.get("secret-key") {
|
||||
self.config.secret_key = secret_key.as_string()?
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No secret key provided",
|
||||
"for command",
|
||||
&call_info.name_tag,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(region) = call_info.args.get("region") {
|
||||
self.config.region = Some(region.as_string()?)
|
||||
}
|
||||
|
||||
ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Handler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn s3_helper(resource: &Value, has_raw: bool, config: &CredentialConfig) -> ReturnValue {
|
||||
let resource_str = resource.as_string()?;
|
||||
let mut handler = S3Handler::from(config);
|
||||
let (output, content_type) = handler
|
||||
.cat(&resource_str)
|
||||
.map_err(|e| ShellError::unexpected(e.to_string()))?;
|
||||
|
||||
let extension = if has_raw {
|
||||
None
|
||||
} else {
|
||||
fn get_accept_ext(s: String) -> Option<String> {
|
||||
if s.contains("json") {
|
||||
Some("json".to_string())
|
||||
} else if s.contains("xml") {
|
||||
Some("xml".to_string())
|
||||
} else if s.contains("svg") {
|
||||
Some("svg".to_string())
|
||||
} else if s.contains("html") {
|
||||
Some("html".to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// If the extension could not provide when uploading,
|
||||
// try to use the resource extension.
|
||||
content_type.and_then(get_accept_ext).or_else(|| {
|
||||
resource_str
|
||||
.split('.')
|
||||
.last()
|
||||
.map(String::from)
|
||||
.and_then(get_accept_ext)
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(e) = extension {
|
||||
Ok(ReturnSuccess::Action(CommandAction::AutoConvert(
|
||||
UntaggedValue::string(output).into_value(Tag {
|
||||
span: resource.tag.span,
|
||||
anchor: Some(AnchorLocation::Url(resource_str)),
|
||||
}),
|
||||
e,
|
||||
)))
|
||||
} else {
|
||||
ReturnSuccess::value(UntaggedValue::string(output))
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
pub mod handler;
|
||||
mod nu;
|
||||
|
||||
pub use handler::Handler;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_s3::handler;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut handler::Handler::new())
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use futures::executor::block_on;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, ReturnValue, Signature, SyntaxShape};
|
||||
|
||||
use crate::handler;
|
||||
use crate::handler::s3_helper;
|
||||
|
||||
impl Plugin for handler::Handler {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("s3")
|
||||
.usage("Load S3 resource into a cell, convert to table if possible (avoid by appending '--raw' or '-R')")
|
||||
.required(
|
||||
"RESOURCE",
|
||||
SyntaxShape::String,
|
||||
"the RESOURCE to fetch the contents from",
|
||||
)
|
||||
.named(
|
||||
"endpoint",
|
||||
SyntaxShape::Any,
|
||||
"the endpoint info for the S3 resource, i.g., s3.ap-northeast-1.amazonaws.com or 10.1.1.1",
|
||||
Some('e'),
|
||||
)
|
||||
.named(
|
||||
"access-key",
|
||||
SyntaxShape::Any,
|
||||
"the access key when authenticating",
|
||||
Some('a'),
|
||||
)
|
||||
.named(
|
||||
"secret-key",
|
||||
SyntaxShape::Any,
|
||||
"the secret key when authenticating",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"region",
|
||||
SyntaxShape::Any,
|
||||
"the region of the resource, default will use us-east-1",
|
||||
Some('r'),
|
||||
)
|
||||
.switch("raw", "fetch contents as text rather than a table", Some('R'))
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.setup(callinfo)?;
|
||||
Ok(vec![block_on(s3_helper(
|
||||
&self.resource.clone().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"internal error: resource not set",
|
||||
"resource not set",
|
||||
&self.tag,
|
||||
)
|
||||
})?,
|
||||
self.has_raw,
|
||||
&self.config,
|
||||
))])
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A plugin to open files/URLs directly from Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_start"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
glob = "0.3.0"
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
url = "2.2.0"
|
||||
webbrowser = "0.5.5"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
open = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
nu-errors = { version = "0.73.1", path="../nu-errors" }
|
||||
nu-source = { version = "0.73.1", path="../nu-source" }
|
|
@ -1,4 +0,0 @@
|
|||
mod nu;
|
||||
mod start;
|
||||
|
||||
pub use start::Start;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_start::Start;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut Start::new());
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, ReturnValue, Signature, SyntaxShape};
|
||||
|
||||
use crate::start::Start;
|
||||
|
||||
impl Plugin for Start {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("start")
|
||||
.usage("Opens each file/directory/URL using the default application")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::String,
|
||||
"files/urls/directories to open",
|
||||
)
|
||||
.named(
|
||||
"application",
|
||||
SyntaxShape::String,
|
||||
"Specifies the application used for opening the files/directories/urls",
|
||||
Some('a'),
|
||||
)
|
||||
.filter())
|
||||
}
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.parse(call_info)?;
|
||||
self.exec().map(|_| Vec::new())
|
||||
}
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_protocol::{CallInfo, Value};
|
||||
use nu_source::{Tag, Tagged, TaggedItem};
|
||||
use std::path::Path;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Start {
|
||||
pub tag: Tag,
|
||||
pub filenames: Vec<Tagged<String>>,
|
||||
pub application: Option<String>,
|
||||
}
|
||||
|
||||
impl Start {
|
||||
pub fn new() -> Start {
|
||||
Start {
|
||||
tag: Tag::unknown(),
|
||||
filenames: vec![],
|
||||
application: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, call_info: CallInfo) -> Result<(), ShellError> {
|
||||
self.tag = call_info.name_tag.clone();
|
||||
self.parse_input_parameters(&call_info)?;
|
||||
self.parse_application(&call_info);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_filename(&mut self, filename: Tagged<String>) -> Result<(), ShellError> {
|
||||
if Path::new(&filename.item).exists() || url::Url::parse(&filename.item).is_ok() {
|
||||
self.filenames.push(filename);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
format!("The file '{}' does not exist", filename.item),
|
||||
"doesn't exist",
|
||||
filename.tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn glob_to_values(&self, value: &Value) -> Result<Vec<Tagged<String>>, ShellError> {
|
||||
let mut result = vec![];
|
||||
match nu_glob::glob(&value.as_string()?) {
|
||||
Ok(paths) => {
|
||||
for path_result in paths {
|
||||
match path_result {
|
||||
Ok(path) => result
|
||||
.push(path.to_string_lossy().to_string().tagged(value.tag.clone())),
|
||||
Err(glob_error) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
glob_error.to_string(),
|
||||
"glob error",
|
||||
value.tag.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(pattern_error) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
pattern_error.to_string(),
|
||||
"invalid pattern",
|
||||
value.tag.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn parse_input_parameters(&mut self, call_info: &CallInfo) -> Result<(), ShellError> {
|
||||
let candidates = match &call_info.args.positional {
|
||||
Some(values) => {
|
||||
let mut result = vec![];
|
||||
|
||||
for value in values {
|
||||
let val_str = value.as_string();
|
||||
match val_str {
|
||||
Ok(s) => {
|
||||
if s.to_ascii_lowercase().starts_with("http")
|
||||
|| s.to_ascii_lowercase().starts_with("https")
|
||||
{
|
||||
if webbrowser::open(&s).is_ok() {
|
||||
result.push("http/web".to_string().tagged_unknown())
|
||||
} else {
|
||||
return Err(ShellError::labeled_error(
|
||||
&format!("error opening {}", &s),
|
||||
"error opening url",
|
||||
self.tag.span,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let res = self.glob_to_values(value)?;
|
||||
result.extend(res);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
e.to_string(),
|
||||
"no input given",
|
||||
self.tag.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No input given",
|
||||
"no input given",
|
||||
self.tag.span,
|
||||
));
|
||||
}
|
||||
result
|
||||
}
|
||||
None => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No input given",
|
||||
"no input given",
|
||||
self.tag.span,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
for candidate in candidates {
|
||||
if !candidate.contains("http/web") {
|
||||
self.add_filename(candidate)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_application(&mut self, call_info: &CallInfo) {
|
||||
self.application = if let Some(app) = call_info.args.get("application") {
|
||||
match app.as_string() {
|
||||
Ok(name) => Some(name),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn exec(&mut self) -> Result<(), ShellError> {
|
||||
let mut args = vec![];
|
||||
args.append(
|
||||
&mut self
|
||||
.filenames
|
||||
.iter()
|
||||
.map(|x| x.item.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
if let Some(app_name) = &self.application {
|
||||
args.append(&mut vec![String::from("-a"), app_name.to_string()]);
|
||||
}
|
||||
exec_cmd("open", &args, self.tag.clone())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn exec(&mut self) -> Result<(), ShellError> {
|
||||
if let Some(app_name) = &self.application {
|
||||
for file in &self.filenames {
|
||||
match open::with(&file.item, app_name) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Failed to open file with specified application",
|
||||
"can't open with specified application",
|
||||
file.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for file in &self.filenames {
|
||||
match open::that(&file.item) {
|
||||
Ok(_) => continue,
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Failed to open file with default application",
|
||||
"can't open with default application",
|
||||
file.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
pub fn exec(&mut self) -> Result<(), ShellError> {
|
||||
let mut args = vec![];
|
||||
args.append(
|
||||
&mut self
|
||||
.filenames
|
||||
.iter()
|
||||
.map(|x| x.item.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
if let Some(app_name) = &self.application {
|
||||
exec_cmd(app_name, &args, self.tag.clone())
|
||||
} else {
|
||||
for cmd in ["xdg-open", "gnome-open", "kde-open", "wslview"] {
|
||||
if exec_cmd(cmd, &args, self.tag.clone()).is_err() {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(ShellError::labeled_error(
|
||||
"Failed to open file(s) with xdg-open. gnome-open, kde-open, and wslview",
|
||||
"failed to open xdg-open. gnome-open, kde-open, and wslview",
|
||||
self.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn exec_cmd(cmd: &str, args: &[String], tag: Tag) -> Result<(), ShellError> {
|
||||
if args.is_empty() {
|
||||
return Err(ShellError::labeled_error(
|
||||
"No file(s) or application provided",
|
||||
"no file(s) or application provided",
|
||||
tag,
|
||||
));
|
||||
}
|
||||
let status = match Command::new(cmd)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.args(args)
|
||||
.status()
|
||||
{
|
||||
Ok(exit_code) => exit_code,
|
||||
Err(_) => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Failed to run native open syscall",
|
||||
"failed to run native open call",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
};
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Failed to run start. Hint: The file(s)/application may not exist",
|
||||
"failed to run",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A converter plugin to the bson format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_to_bson"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
bson = { version = "2.0.1", features = [ "chrono-0_4" ] }
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
num-traits = "0.2.14"
|
||||
|
||||
[features]
|
||||
dataframe = ["nu-protocol/dataframe"]
|
||||
|
||||
[build-dependencies]
|
|
@ -1,4 +0,0 @@
|
|||
mod nu;
|
||||
mod to_bson;
|
||||
|
||||
pub use to_bson::ToBson;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_to_bson::ToBson;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ToBson::new())
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::ToBson;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{ReturnValue, Signature, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
impl Plugin for ToBson {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("to bson")
|
||||
.usage("Convert table into .bson binary")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.state.push(input);
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(crate::to_bson::to_bson(self.state.clone(), Tag::unknown()))
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod integration {}
|
|
@ -1,300 +0,0 @@
|
|||
use bson::{oid::ObjectId, spec::BinarySubtype, Bson, Document};
|
||||
use nu_errors::{CoerceInto, ShellError};
|
||||
use nu_protocol::{
|
||||
Dictionary, Primitive, ReturnSuccess, ReturnValue, SpannedTypeName, UnspannedPathMember,
|
||||
UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Tag, TaggedItem};
|
||||
use num_traits::ToPrimitive;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ToBson {
|
||||
pub state: Vec<Value>,
|
||||
}
|
||||
|
||||
impl ToBson {
|
||||
pub fn new() -> ToBson {
|
||||
ToBson { state: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_to_bson_value(v: &Value) -> Result<Bson, ShellError> {
|
||||
Ok(match &v.value {
|
||||
UntaggedValue::Primitive(Primitive::Boolean(b)) => Bson::Boolean(*b),
|
||||
// FIXME: What about really big decimals?
|
||||
UntaggedValue::Primitive(Primitive::Filesize(decimal)) => Bson::Double(
|
||||
(decimal)
|
||||
.to_f64()
|
||||
.expect("Unimplemented BUG: What about big decimals?"),
|
||||
),
|
||||
UntaggedValue::Primitive(Primitive::Duration(i)) => Bson::String(i.to_string()),
|
||||
UntaggedValue::Primitive(Primitive::Date(d)) => {
|
||||
Bson::DateTime(bson::DateTime::from_chrono(*d))
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::EndOfStream) => Bson::Null,
|
||||
UntaggedValue::Primitive(Primitive::BeginningOfStream) => Bson::Null,
|
||||
UntaggedValue::Primitive(Primitive::Decimal(d)) => {
|
||||
Bson::Double(d.to_f64().ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Could not convert value to decimal",
|
||||
"could not convert to decimal",
|
||||
&v.tag,
|
||||
)
|
||||
})?)
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Int(i)) => Bson::Int64(*i),
|
||||
UntaggedValue::Primitive(Primitive::BigInt(i)) => {
|
||||
Bson::Int64(i.tagged(&v.tag).coerce_into("converting to BSON")?)
|
||||
}
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => Bson::Null,
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => Bson::String(s.clone()),
|
||||
UntaggedValue::Primitive(Primitive::ColumnPath(path)) => Bson::Array(
|
||||
path.iter()
|
||||
.map(|x| match &x.unspanned {
|
||||
UnspannedPathMember::String(string) => Ok(Bson::String(string.clone())),
|
||||
UnspannedPathMember::Int(int) => Ok(Bson::Int64(*int)),
|
||||
})
|
||||
.collect::<Result<Vec<Bson>, ShellError>>()?,
|
||||
),
|
||||
UntaggedValue::Primitive(Primitive::GlobPattern(p)) => Bson::String(p.clone()),
|
||||
UntaggedValue::Primitive(Primitive::FilePath(s)) => Bson::String(s.display().to_string()),
|
||||
UntaggedValue::Table(l) => Bson::Array(
|
||||
l.iter()
|
||||
.map(value_to_bson_value)
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
UntaggedValue::Block(_) | UntaggedValue::Primitive(Primitive::Range(_)) => Bson::Null,
|
||||
#[cfg(feature = "dataframe")]
|
||||
UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => Bson::Null,
|
||||
UntaggedValue::Error(e) => return Err(e.clone()),
|
||||
UntaggedValue::Primitive(Primitive::Binary(b)) => Bson::Binary(bson::Binary {
|
||||
subtype: BinarySubtype::Generic,
|
||||
bytes: b.clone(),
|
||||
}),
|
||||
UntaggedValue::Row(o) => object_value_to_bson(o)?,
|
||||
// TODO Impelmenting Bson::Undefined, Bson::MaxKey, Bson::MinKey and Bson::DbPointer
|
||||
// These Variants weren't present in the previous version.
|
||||
})
|
||||
}
|
||||
|
||||
// object_value_to_bson handles all Objects, even those that correspond to special
|
||||
// types (things like regex or javascript code).
|
||||
fn object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
|
||||
let mut it = o.entries.iter();
|
||||
if it.len() > 2 {
|
||||
return generic_object_value_to_bson(o);
|
||||
}
|
||||
match it.next() {
|
||||
Some((regex, tagged_regex_value)) if regex == "$regex" => match it.next() {
|
||||
Some((options, tagged_opts_value)) if options == "$options" => {
|
||||
let r: Result<String, _> = tagged_regex_value.try_into();
|
||||
let opts: Result<String, _> = tagged_opts_value.try_into();
|
||||
match (r, opts) {
|
||||
(Ok(pattern), Ok(options)) => {
|
||||
Ok(Bson::RegularExpression(bson::Regex { pattern, options }))
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
},
|
||||
Some((javascript, tagged_javascript_value)) if javascript == "$javascript" => {
|
||||
match it.next() {
|
||||
Some((scope, tagged_scope_value)) if scope == "$scope" => {
|
||||
let js: Result<String, _> = tagged_javascript_value.try_into();
|
||||
let s: Result<&Dictionary, _> = tagged_scope_value.try_into();
|
||||
|
||||
match (js, s) {
|
||||
(Ok(code), Ok(s)) => {
|
||||
if let Bson::Document(scope) = object_value_to_bson(s)? {
|
||||
Ok(Bson::JavaScriptCodeWithScope(
|
||||
bson::JavaScriptCodeWithScope { code, scope },
|
||||
))
|
||||
} else {
|
||||
generic_object_value_to_bson(o)
|
||||
}
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let js: Result<String, _> = tagged_javascript_value.try_into();
|
||||
|
||||
match js {
|
||||
Err(_) => generic_object_value_to_bson(o),
|
||||
Ok(v) => Ok(Bson::JavaScriptCode(v)),
|
||||
}
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
Some((timestamp, tagged_timestamp_value)) if timestamp == "$timestamp" => {
|
||||
let ts: Result<i64, _> = tagged_timestamp_value.try_into();
|
||||
if let Ok(time) = ts {
|
||||
Ok(Bson::Timestamp(bson::Timestamp {
|
||||
time: time as u32,
|
||||
increment: Default::default(),
|
||||
}))
|
||||
} else {
|
||||
generic_object_value_to_bson(o)
|
||||
}
|
||||
}
|
||||
Some((binary_subtype, tagged_binary_subtype_value))
|
||||
if binary_subtype == "$binary_subtype" =>
|
||||
{
|
||||
match it.next() {
|
||||
Some((binary, tagged_bin_value)) if binary == "$binary" => {
|
||||
let bst = get_binary_subtype(tagged_binary_subtype_value);
|
||||
let bin: Result<Vec<u8>, _> = tagged_bin_value.try_into();
|
||||
|
||||
match (bin, bst) {
|
||||
(Ok(bin), Ok(subtype)) => Ok(Bson::Binary(bson::Binary {
|
||||
subtype,
|
||||
bytes: bin,
|
||||
})),
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
Some((object_id, tagged_object_id_value)) if object_id == "$object_id" => {
|
||||
let obj_id: Result<String, _> = tagged_object_id_value.try_into();
|
||||
|
||||
if let Ok(obj_id) = obj_id {
|
||||
let obj_id = ObjectId::parse_str(&obj_id);
|
||||
|
||||
if let Ok(obj_id) = obj_id {
|
||||
Ok(Bson::ObjectId(obj_id))
|
||||
} else {
|
||||
generic_object_value_to_bson(o)
|
||||
}
|
||||
} else {
|
||||
generic_object_value_to_bson(o)
|
||||
}
|
||||
}
|
||||
Some((symbol, tagged_symbol_value)) if symbol == "$symbol" => {
|
||||
let sym: Result<String, _> = tagged_symbol_value.try_into();
|
||||
if let Ok(sym) = sym {
|
||||
Ok(Bson::Symbol(sym))
|
||||
} else {
|
||||
generic_object_value_to_bson(o)
|
||||
}
|
||||
}
|
||||
_ => generic_object_value_to_bson(o),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binary_subtype(tagged_value: &Value) -> Result<BinarySubtype, ShellError> {
|
||||
match &tagged_value.value {
|
||||
UntaggedValue::Primitive(Primitive::String(s)) => Ok(match s.as_ref() {
|
||||
"generic" => BinarySubtype::Generic,
|
||||
"function" => BinarySubtype::Function,
|
||||
"binary_old" => BinarySubtype::BinaryOld,
|
||||
"uuid_old" => BinarySubtype::UuidOld,
|
||||
"uuid" => BinarySubtype::Uuid,
|
||||
"md5" => BinarySubtype::Md5,
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
UntaggedValue::Primitive(Primitive::BigInt(i)) => Ok(BinarySubtype::UserDefined(
|
||||
i.tagged(&tagged_value.tag)
|
||||
.coerce_into("converting to BSON binary subtype")?,
|
||||
)),
|
||||
_ => Err(ShellError::type_error(
|
||||
"bson binary",
|
||||
tagged_value.spanned_type_name(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// generic_object_value_bson handles any Object that does not
|
||||
// correspond to a special bson type (things like regex or javascript code).
|
||||
fn generic_object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
|
||||
let mut doc = Document::new();
|
||||
for (k, v) in &o.entries {
|
||||
doc.insert(k.clone(), value_to_bson_value(v)?);
|
||||
}
|
||||
Ok(Bson::Document(doc))
|
||||
}
|
||||
|
||||
fn shell_encode_document(writer: &mut Vec<u8>, doc: Document, tag: Tag) -> Result<(), ShellError> {
|
||||
match doc.to_writer(writer) {
|
||||
Err(e) => Err(ShellError::labeled_error(
|
||||
format!("Failed to encode document due to: {:?}", e),
|
||||
"requires BSON-compatible document",
|
||||
tag,
|
||||
)),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn bson_value_to_bytes(bson: Bson, tag: Tag) -> Result<Vec<u8>, ShellError> {
|
||||
let mut out = Vec::new();
|
||||
match bson {
|
||||
Bson::Array(a) => {
|
||||
for v in a {
|
||||
match v {
|
||||
Bson::Document(d) => shell_encode_document(&mut out, d, tag.clone())?,
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
format!("All top level values must be Documents, got {:?}", v),
|
||||
"requires BSON-compatible document",
|
||||
&tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Bson::Document(d) => shell_encode_document(&mut out, d, tag)?,
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
format!("All top level values must be Documents, got {:?}", bson),
|
||||
"requires BSON-compatible document",
|
||||
tag,
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn to_bson(input: Vec<Value>, name_tag: Tag) -> Vec<ReturnValue> {
|
||||
let name_span = name_tag.span;
|
||||
|
||||
let to_process_input = match input.len() {
|
||||
x if x > 1 => {
|
||||
let tag = input[0].tag.clone();
|
||||
vec![Value {
|
||||
value: UntaggedValue::Table(input),
|
||||
tag,
|
||||
}]
|
||||
}
|
||||
1 => input,
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
to_process_input
|
||||
.into_iter()
|
||||
.map(move |value| match value_to_bson_value(&value) {
|
||||
Ok(bson_value) => {
|
||||
let value_span = value.tag.span;
|
||||
|
||||
match bson_value_to_bytes(bson_value, name_tag.clone()) {
|
||||
Ok(x) => ReturnSuccess::value(UntaggedValue::binary(x).into_value(name_span)),
|
||||
_ => Err(ShellError::labeled_error_with_secondary(
|
||||
"Expected a table with BSON-compatible structure from pipeline",
|
||||
"requires BSON-compatible input",
|
||||
name_span,
|
||||
"originates from here".to_string(),
|
||||
value_span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a table with BSON-compatible structure from pipeline",
|
||||
"requires BSON-compatible input",
|
||||
&name_tag,
|
||||
)),
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "A converter plugin to the SQLite format for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_to_sqlite"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4.2"
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
nu-source = { path="../nu-source", version = "0.73.1" }
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[dependencies.rusqlite]
|
||||
features = ["bundled", "blob"]
|
||||
version = "0.26.1"
|
||||
|
||||
[build-dependencies]
|
|
@ -1,4 +0,0 @@
|
|||
mod nu;
|
||||
mod to_sqlite;
|
||||
|
||||
pub use to_sqlite::ToSqlite;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_to_sqlite::ToSqlite;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut ToSqlite::new())
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use crate::ToSqlite;
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{ReturnValue, Signature, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
impl Plugin for ToSqlite {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("to sqlite")
|
||||
.usage("Convert table into sqlite binary")
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
self.state.push(input);
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn end_filter(&mut self) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
crate::to_sqlite::to_sqlite(self.state.clone(), Tag::unknown())
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod integration {}
|
|
@ -1,172 +0,0 @@
|
|||
use hex::encode;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Primitive, ReturnSuccess, ReturnValue, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use rusqlite::Connection;
|
||||
use std::io::Read;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ToSqlite {
|
||||
pub state: Vec<Value>,
|
||||
}
|
||||
|
||||
impl ToSqlite {
|
||||
pub fn new() -> ToSqlite {
|
||||
ToSqlite { state: vec![] }
|
||||
}
|
||||
}
|
||||
fn comma_concat(acc: String, current: String) -> String {
|
||||
if acc.is_empty() {
|
||||
current
|
||||
} else {
|
||||
format!("{}, {}", acc, current)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_columns(rows: &[Value]) -> Result<String, std::io::Error> {
|
||||
match &rows[0].value {
|
||||
UntaggedValue::Row(d) => Ok(d
|
||||
.entries
|
||||
.iter()
|
||||
.map(|(k, _v)| k.clone())
|
||||
.fold("".to_string(), comma_concat)),
|
||||
_ => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Could not find table column names",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn nu_value_to_sqlite_string(v: Value) -> String {
|
||||
match &v.value {
|
||||
UntaggedValue::Primitive(p) => match p {
|
||||
Primitive::Nothing => "NULL".into(),
|
||||
Primitive::BigInt(i) => i.to_string(),
|
||||
Primitive::Int(i) => i.to_string(),
|
||||
Primitive::Duration(i) => i.to_string(),
|
||||
Primitive::Decimal(f) => f.to_string(),
|
||||
Primitive::Filesize(u) => u.to_string(),
|
||||
Primitive::GlobPattern(s) => format!("'{}'", s.replace("'", "''")),
|
||||
Primitive::String(s) => format!("'{}'", s.replace("'", "''")),
|
||||
Primitive::Boolean(true) => "1".into(),
|
||||
Primitive::Boolean(_) => "0".into(),
|
||||
Primitive::Date(d) => format!("'{}'", d),
|
||||
Primitive::FilePath(p) => format!("'{}'", p.display().to_string().replace("'", "''")),
|
||||
Primitive::Binary(u) => format!("x'{}'", encode(u)),
|
||||
Primitive::BeginningOfStream
|
||||
| Primitive::EndOfStream
|
||||
| Primitive::ColumnPath(_)
|
||||
| Primitive::Range(_) => "NULL".into(),
|
||||
},
|
||||
_ => "NULL".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_insert_values(rows: Vec<Value>) -> Result<String, std::io::Error> {
|
||||
let values: Result<Vec<_>, _> = rows
|
||||
.into_iter()
|
||||
.map(|value| match value.value {
|
||||
UntaggedValue::Row(d) => Ok(format!(
|
||||
"({})",
|
||||
d.entries
|
||||
.iter()
|
||||
.map(|(_k, v)| nu_value_to_sqlite_string(v.clone()))
|
||||
.fold("".to_string(), comma_concat)
|
||||
)),
|
||||
_ => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Could not find table column names",
|
||||
)),
|
||||
})
|
||||
.collect();
|
||||
let values = values?;
|
||||
Ok(values.join(", "))
|
||||
}
|
||||
|
||||
fn generate_statements(table: Dictionary) -> Result<(String, String), std::io::Error> {
|
||||
let table_name = match table.entries.get("table_name") {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(table_name)),
|
||||
..
|
||||
}) => table_name,
|
||||
_ => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Could not find table name",
|
||||
))
|
||||
}
|
||||
};
|
||||
let (columns, insert_values) = match table.entries.get("table_values") {
|
||||
Some(Value {
|
||||
value: UntaggedValue::Table(l),
|
||||
..
|
||||
}) => {
|
||||
if l.is_empty() {
|
||||
return Ok((String::new(), String::new()));
|
||||
}
|
||||
(get_columns(l), get_insert_values(l.to_vec()))
|
||||
}
|
||||
_ => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Could not find table values",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let create = format!("create table {}({})", table_name, columns?);
|
||||
let insert = format!("insert into {} values {}", table_name, insert_values?);
|
||||
Ok((create, insert))
|
||||
}
|
||||
|
||||
fn sqlite_input_stream_to_bytes(values: Vec<Value>) -> Result<Value, std::io::Error> {
|
||||
// FIXME: should probably write a sqlite virtual filesystem
|
||||
// that will allow us to use bytes as a file to avoid this
|
||||
// write out, but this will require C code. Might be
|
||||
// best done as a PR to rusqlite.
|
||||
let mut tempfile = tempfile::NamedTempFile::new()?;
|
||||
let conn = match Connection::open(tempfile.path()) {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
};
|
||||
let tag = values[0].tag.clone();
|
||||
for value in values {
|
||||
match &value.value {
|
||||
UntaggedValue::Row(d) => {
|
||||
let (create, insert) = generate_statements(d.to_owned())?;
|
||||
if create.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match conn
|
||||
.execute(&create, [])
|
||||
.and_then(|_| conn.execute(&insert, []))
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("Expected row, found {:?}", other),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
tempfile.read_to_end(&mut out)?;
|
||||
Ok(UntaggedValue::binary(out).into_value(tag))
|
||||
}
|
||||
|
||||
pub fn to_sqlite(input: Vec<Value>, name_tag: Tag) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
match sqlite_input_stream_to_bytes(input) {
|
||||
Ok(out) => Ok(vec![ReturnSuccess::value(out)]),
|
||||
_ => Err(ShellError::labeled_error(
|
||||
"Expected a table with SQLite-compatible structure from pipeline",
|
||||
"requires SQLite-compatible input",
|
||||
name_tag,
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
authors = ["The Nushell Project Developers"]
|
||||
description = "Tree viewer plugin for Nushell"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_tree"
|
||||
version = "0.73.1"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
derive-new = "0.5.8"
|
||||
nu-errors = { path="../nu-errors", version = "0.73.1" }
|
||||
nu-plugin = { path="../nu-plugin", version = "0.73.1" }
|
||||
nu-protocol = { path="../nu-protocol", version = "0.73.1" }
|
||||
ptree = { version = "0.4.0", default-features = false }
|
||||
|
||||
|
||||
[build-dependencies]
|
|
@ -1,4 +0,0 @@
|
|||
mod nu;
|
||||
mod tree;
|
||||
|
||||
pub use tree::TreeViewer;
|
|
@ -1,6 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_tree::TreeViewer;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut TreeViewer);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, Signature, Value};
|
||||
|
||||
use crate::tree::TreeView;
|
||||
use crate::TreeViewer;
|
||||
|
||||
impl Plugin for TreeViewer {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("tree").usage("View the contents of the pipeline as a tree."))
|
||||
}
|
||||
|
||||
fn sink(&mut self, _call_info: CallInfo, input: Vec<Value>) {
|
||||
for i in &input {
|
||||
let view = TreeView::from_value(i);
|
||||
let _ = view.render_view();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{format_primitive, UntaggedValue, Value};
|
||||
use ptree::item::StringItem;
|
||||
use ptree::output::print_tree_with;
|
||||
use ptree::print_config::PrintConfig;
|
||||
use ptree::style::{Color, Style};
|
||||
use ptree::TreeBuilder;
|
||||
|
||||
pub struct TreeViewer;
|
||||
#[derive(new)]
|
||||
pub struct TreeView {
|
||||
tree: StringItem,
|
||||
}
|
||||
|
||||
impl TreeView {
|
||||
fn from_value_helper(value: &UntaggedValue, mut builder: &mut TreeBuilder) {
|
||||
match value {
|
||||
UntaggedValue::Primitive(p) => {
|
||||
let _ = builder.add_empty_child(format_primitive(p, None));
|
||||
}
|
||||
UntaggedValue::Row(o) => {
|
||||
for (k, v) in &o.entries {
|
||||
builder = builder.begin_child(k.clone());
|
||||
Self::from_value_helper(v, builder);
|
||||
builder = builder.end_child();
|
||||
}
|
||||
}
|
||||
UntaggedValue::Table(l) => {
|
||||
for elem in l {
|
||||
Self::from_value_helper(elem, builder);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: &Value) -> TreeView {
|
||||
let descs = value.data_descriptors();
|
||||
|
||||
let mut tree = TreeBuilder::new("".to_string());
|
||||
let mut builder = &mut tree;
|
||||
|
||||
for desc in descs {
|
||||
let value = match &value.value {
|
||||
UntaggedValue::Row(d) => d.get_data(&desc).borrow().clone(),
|
||||
_ => value.clone(),
|
||||
};
|
||||
builder = builder.begin_child(desc.clone());
|
||||
Self::from_value_helper(&value, builder);
|
||||
builder = builder.end_child();
|
||||
//entries.push((desc.name.clone(), value.borrow().copy()))
|
||||
}
|
||||
|
||||
TreeView::new(builder.build())
|
||||
}
|
||||
|
||||
pub fn render_view(&self) -> Result<(), ShellError> {
|
||||
// Set up the print configuration
|
||||
let config = {
|
||||
let mut config = PrintConfig::from_env();
|
||||
config.branch = Style {
|
||||
foreground: Some(Color::Green),
|
||||
dimmed: true,
|
||||
..Style::default()
|
||||
};
|
||||
config.leaf = Style {
|
||||
bold: true,
|
||||
..Style::default()
|
||||
};
|
||||
config.indent = 4;
|
||||
config
|
||||
};
|
||||
|
||||
// Print out the tree using custom formatting
|
||||
print_tree_with(&self.tree, &config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue