chore: add plugins

This commit is contained in:
Jesús Pérex 2025-06-27 02:31:23 +01:00
parent 57cf519bbe
commit b1fbce5b3c
159 changed files with 26723 additions and 0 deletions

View File

@ -0,0 +1,25 @@
name: Publish Crate
on:
release:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Publish to crates.io
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

3
nu_plugin_clipboard/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
.vscode
.idea/

2316
nu_plugin_clipboard/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
[package]
name = "nu_plugin_clipboard"
license = "MIT"
authors = [
"Motalleb Fallahnezhad <fmotalleb@gmail.com>",
]
keywords = [
"nushell",
"clipboard",
"plugin",
]
homepage = "https://github.com/FMotalleb/nu_plugin_clipboard"
repository = "https://github.com/FMotalleb/nu_plugin_clipboard"
description = "A nushell plugin to copy text into clipboard or get text from it."
version = "0.105.2"
edition = "2024"
readme = "README.md"
[dependencies]
nu-plugin = { version = "0.105.2", path = "../nushell/crates/nu-plugin" }
nu-protocol = { version = "0.105.2", features = ["plugin"] , path = "../nushell/crates/nu-protocol" }
arboard = { version = "3.5.0", default-features = false }
nu-json = { path = "../nushell/crates/nu-json", version = "0.105.2" }
[features]
default = [
]
use-wayland = [
"arboard/wayland-data-control",
]
debug = [
]

View File

@ -0,0 +1,7 @@
Copyright 2023 "Motalleb Fallahnezhad"
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,95 @@
# 📋 nu_plugin_clipboard
A [nushell](https://www.nushell.sh/) plugin for interacting with the clipboard, allowing you to copy/paste text, objects, and tables.
## ✨ Features
- **`clipboard copy`**: Copies input text to the clipboard.
- **Daemon Behavior:** Since version **0.105.2**, using env variables will try to detect display server.
This config will override this behavior, if you need to override this please report and issue:
```bash
$env.config.plugins.clipboard.NO_DAEMON = true
```
- To make this setting permanent, add it to your `config env`.
- **`clipboard paste`**: Retrieves the current clipboard content.
## ⚠️ Important
If you face the error `Error: × Clipboard Error: The clipboard contents were not available in the requested format...`
Try disabling the daemon mode, as mentioned in [#20](https://github.com/FMotalleb/nu_plugin_clipboard/issues/20).
## 📌 Usage Examples
### Copying a string (supports only strings for now)
```bash
echo "test value" | clipboard copy
```
### Using clipboard content
```bash
clipboard paste | echo $in
```
### Copying tables and objects
- Tables and objects are internally converted to **JSON**.
- When pasting, `clipboard paste` tries to parse JSON into a table or object.
- If parsing fails, the content is returned as a string.
```bash
$env | clipboard copy
clipboard paste
ps | clipboard copy
clipboard paste
```
## 🔧 Installation
### 🚀 Recommended: Using [nupm](https://github.com/nushell/nupm)
This method automatically handles dependencies and features:
```bash
git clone https://github.com/FMotalleb/nu_plugin_clipboard.git
nupm install --path nu_plugin_clipboard -f
```
### ⚙️ Supported Features
- **`use-wayland`**: Prioritizes the Wayland API, but falls back to X11 if needed.
- **`enforce-daemon`**: _(Deprecated)_ Now always enabled on Linux. Disable with:
```bash
$env.config.plugins.clipboard.NO_DAEMON = true
```
### 🛠️ Manual Compilation
```bash
git clone https://github.com/FMotalleb/nu_plugin_clipboard.git
cd nu_plugin_clipboard
cargo build -r
plugin add target/release/nu_plugin_clipboard
```
### 📦 Install via Cargo (using git)
```bash
cargo install --git https://github.com/FMotalleb/nu_plugin_clipboard.git
plugin add ~/.cargo/bin/nu_plugin_clipboard
```
### 📦 Install via Cargo (crates.io) _Not Recommended_
- Since I live in Iran and crates.io won't let me update my packages like a normal person, most of the time crates.io is outdated.
```bash
cargo install nu_plugin_clipboard
plugin add ~/.cargo/bin/nu_plugin_clipboard
```

View File

@ -0,0 +1,44 @@
use std log
let messages = {
"use-wayland" : $"Found (ansi blue)wayland(ansi reset) in env\(`(ansi blue)XDG_SESSION_TYPE(ansi reset)`\): activating `(ansi green)use-wayland(ansi reset)` feature"
"debug" : $"Debug mode is enabled: activating `(ansi green)debug(ansi reset)` feature"
}
def append-feature [active: bool,feature: string] {
if $active {
$in | append $feature
} else {
$in
}
}
def main [package_file: path = nupm.nuon] {
let repo_root = (ls -f $package_file | first | get name | path dirname)
let install_root = $env.NUPM_HOME | path join "plugins"
let name = open ($repo_root | path join "Cargo.toml") | get package.name
let debug = (([no,yes] | input list "Enable debug mode (do not enable unless u wanna debug)") == "yes")
let use_wayland = ($nu.os-info.name == "linux" and ($env.XDG_SESSION_TYPE? == "wayland"))
let features = []
| append-feature $use_wayland use-wayland
| append-feature $debug debug
for feature in $features {
let message = $messages | get $feature
if $message != null {
log info $message
}
}
let channel = $repo_root
| path join rust-toolchain.toml
| open $in
| get toolchain?.channel?
| default stable
let cmd = $"cargo +($channel) install --path ($repo_root) --root ($install_root) --features=($features | str join ",")"
log info $"building plugin using: (ansi blue)($cmd)(ansi reset)"
nu -c $cmd
let ext: string = if ($nu.os-info.name == 'windows') { '.exe' } else { '' }
plugin add $"($install_root | path join "bin" $name)($ext)"
log info "do not forget to restart Nushell for the plugin to be fully available!"
}

View File

@ -0,0 +1,7 @@
{
"name": "nu_plugin_clipboard",
"version": "0.105.2",
"description": "A nushell plugin to copy text into clipboard or get text from it.",
"license": "LICENSE",
"type": "custom"
}

View File

@ -0,0 +1,3 @@
[toolchain]
profile = "default"
channel = "stable"

View File

@ -0,0 +1,12 @@
use super::error_mapper::map_arboard_err_to_label;
use nu_protocol::LabeledError;
pub(crate) fn with_clipboard_instance<
U,
F: FnOnce(&mut arboard::Clipboard) -> Result<U, arboard::Error>,
>(
op: F,
) -> Result<U, LabeledError> {
let mut clipboard = arboard::Clipboard::new().map_err(map_arboard_err_to_label)?;
op(&mut clipboard).map_err(map_arboard_err_to_label)
}

View File

@ -0,0 +1,60 @@
#[cfg(target_os = "linux")]
use std::io::Error;
use nu_protocol::LabeledError;
use super::arboard_provider::with_clipboard_instance;
pub enum CheckResult {
Continue,
#[cfg(target_os = "linux")]
Exit(String, i32),
}
#[cfg(target_os = "linux")]
fn no_daemon(config: Option<nu_protocol::Value>) -> Result<bool, Error> {
use crate::clipboard::detect_display::DisplayServer;
match config {
None => Ok(!DisplayServer::should_use_daemon()),
Some(nu_protocol::Value::Record { val, .. }) => {
return no_daemon(val.get("NO_DAEMON").cloned());
}
Some(nu_protocol::Value::Bool { val, .. }) => Ok(val),
Some(nu_protocol::Value::String { val, .. }) => match val.as_str() {
"true" | "True" | "1" => Ok(true),
_ => Ok(!DisplayServer::should_use_daemon()),
},
Some(nu_protocol::Value::Int { val, .. }) => Ok(val == 1),
_ => Ok(!DisplayServer::should_use_daemon()),
}
}
#[cfg(target_os = "linux")]
pub fn create_clipboard(config: Option<nu_protocol::Value>) -> impl Clipboard {
crate::clipboard::linux::ClipBoardLinux::new(!no_daemon(config).unwrap_or(false))
}
#[cfg(not(target_os = "linux"))]
pub fn create_clipboard(_: Option<nu_protocol::Value>) -> impl Clipboard {
#[cfg(target_os = "macos")]
{
crate::clipboard::mac_os::ClipBoardMacos::new()
}
#[cfg(target_os = "windows")]
{
crate::clipboard::windows::ClipBoardWindows::new()
}
}
pub trait Clipboard {
fn pre_execute_check(&self) -> CheckResult {
CheckResult::Continue
}
fn copy_text(&self, text: &str) -> Result<(), LabeledError> {
with_clipboard_instance(|clip| clip.set_text(text))
}
fn get_text(&self) -> Result<String, LabeledError> {
with_clipboard_instance(|clip| clip.get_text())
}
}

View File

@ -0,0 +1,48 @@
use std::env;
use crate::debug_println;
pub enum DisplayServer {
Wayland,
X11,
Unknown(String),
None,
}
impl DisplayServer {
fn detect() -> DisplayServer {
if let Ok(session_type) = env::var("XDG_SESSION_TYPE") {
debug_println!("found XDG_SESSION: {}", session_type);
match session_type.to_lowercase().as_str() {
"wayland" => DisplayServer::Wayland,
"x11" => DisplayServer::X11,
_ => DisplayServer::Unknown(session_type),
}
} else if env::var("WAYLAND_DISPLAY").is_ok() {
debug_println!("WAYLAND_DISPLAY is found");
DisplayServer::Wayland
} else if env::var("DISPLAY").is_ok() {
debug_println!("DISPLAY (X11) is found");
DisplayServer::X11
} else {
DisplayServer::None
}
}
pub fn should_use_daemon() -> bool {
match Self::detect() {
DisplayServer::Wayland => false,
DisplayServer::X11 => true,
// Iam not sure about these values, let's assume they do not require a daemon
DisplayServer::Unknown(srv) => {
eprintln!(
"Unknown display server detected, assuming no daemon is needed, please report your display server's name in issue tracker. {}
If you experience issues with clipboard, follow daemon settings in https://github.com/FMotalleb/nu_plugin_clipboard.",
srv,
);
false
}
DisplayServer::None => false,
}
}
}

View File

@ -0,0 +1,3 @@
pub(crate) fn map_arboard_err_to_label(err: arboard::Error) -> nu_protocol::LabeledError {
nu_protocol::LabeledError::new(format!("Clipboard Error: {}", err.to_string()))
}

View File

@ -0,0 +1,103 @@
use std::{
env,
io::{stderr, stdout, Read, Write},
process::{Command, Stdio},
};
use super::{
arboard_provider::with_clipboard_instance,
clipboard::{CheckResult, Clipboard},
};
use arboard::Error;
use nu_protocol::LabeledError;
const DAEMONIZE_ARG: &str = "daemon-copy";
pub(crate) struct ClipBoardLinux {
pub(crate) use_daemon: bool,
}
impl ClipBoardLinux {
pub fn new(use_daemon: bool) -> Self {
Self { use_daemon }
}
fn is_daemon_request() -> bool {
env::args().nth(1).as_deref() == Some(DAEMONIZE_ARG)
}
fn request_daemon(&self, text: &str) -> Result<(), nu_protocol::LabeledError> {
match env::current_exe().map(|exe| spawn_daemon(text, exe)) {
Ok(res) => res,
Err(err) => Err(nu_protocol::LabeledError::new(format!(
"Failed to spawn daemon process: {}",
err.to_string()
))),
}
}
fn copy_with_daemon() -> Result<(), LabeledError> {
with_clipboard_instance(|clip: &mut arboard::Clipboard| {
clip.clear()?;
let mut input = String::new();
if let Err(err) = std::io::stdin().read_to_string(&mut input) {
return Err(Error::Unknown {
description: format!("Failed to read from stdin: {}", err.to_string()),
});
}
clip.set_text(input)
})
}
}
fn spawn_daemon(text: &str, exe: std::path::PathBuf) -> Result<(), LabeledError> {
let child = Command::new(exe)
.arg(DAEMONIZE_ARG)
.stdin(Stdio::piped())
.stdout(stdout())
.stderr(stderr())
.current_dir(env::temp_dir())
.spawn();
if let Err(err) = child {
return Err(LabeledError::new(format!(
"failed to spawn daemon process: {}",
err
)));
}
let mut child = child.unwrap();
if let Some(mut stdin) = child.stdin.take() {
if let Err(err) = stdin.write_all(text.as_bytes()) {
return Err(LabeledError::new(format!(
"Failed to write to stdin: {}",
err
)));
}
}
// Optionally, wait for the child process to finish
let status = child.wait();
status
.map(|_| ())
.map_err(|e| LabeledError::new(e.to_string()))
}
impl Clipboard for ClipBoardLinux {
fn pre_execute_check(&self) -> CheckResult {
match Self::is_daemon_request() {
true => match Self::copy_with_daemon() {
Err(e) => CheckResult::Exit(e.msg, 1),
_ => CheckResult::Exit("".to_string(), 0),
},
false => CheckResult::Continue,
}
}
fn copy_text(&self, text: &str) -> Result<(), LabeledError> {
if self.use_daemon {
self.request_daemon(text)
} else {
with_clipboard_instance(|clip: &mut arboard::Clipboard| clip.set_text(text))
}
}
}

View File

@ -0,0 +1,11 @@
use super::clipboard::Clipboard;
pub(crate) struct ClipBoardMacos;
impl ClipBoardMacos {
pub fn new() -> Self {
Self {}
}
}
impl Clipboard for ClipBoardMacos {}

View File

@ -0,0 +1,14 @@
mod arboard_provider;
pub mod clipboard;
mod error_mapper;
#[cfg(target_os = "linux")]
mod detect_display;
#[cfg(target_os = "linux")]
pub(crate) mod linux;
#[cfg(target_os = "macos")]
pub(crate) mod mac_os;
#[cfg(target_os = "windows")]
pub(crate) mod windows;

View File

@ -0,0 +1,11 @@
use super::clipboard::Clipboard;
pub(crate) struct ClipBoardWindows;
impl ClipBoardWindows {
pub fn new() -> Self {
Self {}
}
}
impl Clipboard for ClipBoardWindows {}

View File

@ -0,0 +1,77 @@
use crate::clipboard::clipboard::Clipboard;
use crate::ClipboardPlugins;
use crate::{clipboard::clipboard::create_clipboard, utils::json};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{Category, IntoPipelineData, LabeledError, PipelineData, Signature, Type, Value};
pub struct ClipboardCopy;
impl ClipboardCopy {
pub fn new() -> ClipboardCopy {
ClipboardCopy {}
}
fn format_json(input: &Value) -> Result<String, LabeledError> {
let json_value =
json::value_to_json_value(&input).map(|v| nu_json::to_string_with_indent(&v, 4));
match json_value {
Ok(Ok(text)) => Ok(text.to_owned()), // Return the owned String
Ok(Err(err)) => Err(LabeledError::new(format!(
"JSON Serialization Error: {}",
err,
))),
Err(err) => Err(LabeledError::new(format!("JSON Conversion Error: {}", err))),
}
}
fn copy(engine: &EngineInterface, input: &Value) -> Result<(), LabeledError> {
let text: Result<String, LabeledError> = match input {
Value::String { val, .. } => Ok(val.to_owned()),
_ => Self::format_json(input),
};
match text.map(|text| {
create_clipboard(engine.get_plugin_config().ok().unwrap_or(None))
.copy_text(text.as_str())
}) {
Ok(Ok(_)) => Ok(()),
Err(err) | Ok(Err(err)) => Err(err),
}
}
}
impl PluginCommand for ClipboardCopy {
type Plugin = ClipboardPlugins;
fn name(&self) -> &str {
"clipboard copy"
}
fn signature(&self) -> Signature {
Signature::build("clipboard copy")
.input_output_types(vec![(Type::Any, Type::Any)])
.category(Category::System)
}
fn run(
&self,
_plugin: &Self::Plugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let value = input.into_value(call.head);
match value {
Ok(value) => {
if let Err(err) = Self::copy(engine, &value) {
return Err(err);
}
Ok(value.into_pipeline_data())
}
Err(err) => Err(LabeledError::new(err.to_string())),
}
}
fn description(&self) -> &str {
"copy the input into the clipboard"
}
}

View File

@ -0,0 +1,2 @@
pub mod copy;
pub mod paste;

View File

@ -0,0 +1,62 @@
use crate::{
clipboard::clipboard::{create_clipboard, Clipboard},
utils::json::json_to_value,
ClipboardPlugins,
};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{Category, IntoPipelineData, LabeledError, PipelineData, Type, Value};
pub struct ClipboardPaste;
impl ClipboardPaste {
pub fn new() -> Self {
Self
}
}
impl PluginCommand for ClipboardPaste {
type Plugin = ClipboardPlugins;
fn name(&self) -> &str {
"clipboard paste"
}
fn signature(&self) -> nu_protocol::Signature {
nu_protocol::Signature::build("clipboard paste")
.switch("raw", "disable json formatting", Some('r'))
.input_output_types(vec![(Type::Nothing, Type::Any)])
.category(Category::System)
}
fn description(&self) -> &str {
"Outputs the current value in clipboard"
}
fn run(
&self,
_plugin: &Self::Plugin,
engine: &EngineInterface,
call: &EvaluatedCall,
_input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let text = create_clipboard(engine.get_plugin_config().ok().flatten()).get_text()?;
if text.trim().is_empty() {
return Err(LabeledError::new("Empty clipboard".to_string()));
}
if call.has_flag("raw").unwrap_or(false) {
return Ok(Value::string(text, call.head).into_pipeline_data());
}
match nu_json::from_str::<nu_json::Value>(&text) {
Ok(value) => json_to_value(value, call.head).map(|v| v.into_pipeline_data()),
Err(nu_json::Error::Syntax(_, _, _)) => {
Ok(Value::string(text, call.head).into_pipeline_data())
}
Err(e) => Err(LabeledError::new(format!(
"JSON Deserialization error: {}",
e
))),
}
}
}

View File

@ -0,0 +1,47 @@
mod clipboard;
mod command;
pub mod utils;
use std::io;
#[cfg(target_os = "linux")]
use std::{
io::{stderr, stdout, Write},
process::exit,
};
use crate::command::copy::ClipboardCopy;
use crate::command::paste::ClipboardPaste;
use clipboard::clipboard::{create_clipboard, CheckResult, Clipboard};
use nu_plugin::PluginCommand;
pub struct ClipboardPlugins;
impl nu_plugin::Plugin for ClipboardPlugins {
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
vec![
Box::new(ClipboardCopy::new()),
Box::new(ClipboardPaste::new()),
]
}
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
}
fn main() -> Result<(), io::Error> {
match create_clipboard(None).pre_execute_check() {
CheckResult::Continue => Ok(nu_plugin::serve_plugin(
&mut ClipboardPlugins {},
nu_plugin::MsgPackSerializer {},
)),
#[cfg(target_os = "linux")]
CheckResult::Exit(message, code) => {
if code != 0 {
writeln!(stderr(), "Error ({}): {}", code, message)?;
} else if !message.is_empty() {
writeln!(stdout(), "{}", message)?;
}
exit(code)
}
}
}

View File

@ -0,0 +1,87 @@
use nu_protocol::ast::PathMember;
use nu_protocol::{LabeledError, Record, ShellError, Span, Value};
pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
let span = v.span();
Ok(match v {
Value::Bool { val, .. } => nu_json::Value::Bool(*val),
Value::Filesize { val, .. } => nu_json::Value::I64(val.get()),
Value::Duration { val, .. } => nu_json::Value::I64(*val),
Value::Date { val, .. } => nu_json::Value::String(val.to_string()),
Value::Float { val, .. } => nu_json::Value::F64(*val),
Value::Int { val, .. } => nu_json::Value::I64(*val),
Value::Nothing { .. } => nu_json::Value::Null,
Value::String { val, .. } => nu_json::Value::String(val.to_string()),
Value::Glob { val, .. } => nu_json::Value::String(val.to_string()),
Value::CellPath { val, .. } => nu_json::Value::Array(
val.members
.iter()
.map(|x| match &x {
PathMember::String { val, .. } => Ok(nu_json::Value::String(val.clone())),
PathMember::Int { val, .. } => Ok(nu_json::Value::U64(*val as u64)),
})
.collect::<Result<Vec<nu_json::Value>, ShellError>>()?,
),
Value::List { vals, .. } => nu_json::Value::Array(json_list(vals)?),
Value::Error { error, .. } => return Err(*error.clone()),
Value::Closure { .. } | Value::Range { .. } => nu_json::Value::Null,
Value::Binary { val, .. } => {
nu_json::Value::Array(val.iter().map(|x| nu_json::Value::U64(*x as u64)).collect())
}
Value::Record { val, .. } => {
let mut m = nu_json::Map::new();
for (k, v) in &**val {
m.insert(k.clone(), value_to_json_value(v)?);
}
nu_json::Value::Object(m)
}
Value::Custom { val, .. } => {
let collected = val.to_base_value(span)?;
value_to_json_value(&collected)?
}
})
}
pub fn json_list(input: &[Value]) -> Result<Vec<nu_json::Value>, ShellError> {
let mut out = vec![];
for value in input {
out.push(value_to_json_value(value)?);
}
Ok(out)
}
pub fn json_to_value(v: nu_json::Value, span: Span) -> Result<Value, LabeledError> {
Ok(match v {
nu_json::Value::Null => Value::nothing(span),
nu_json::Value::Bool(val) => Value::bool(val, span),
nu_json::Value::I64(val) => Value::int(val, span),
nu_json::Value::U64(val) => {
if val <= i64::MAX as u64 {
let val = val as i64;
Value::int(val, span)
} else {
Value::string(format!("{}", val), span)
}
}
nu_json::Value::F64(val) => Value::float(val, span),
nu_json::Value::String(val) => Value::string(val, span),
nu_json::Value::Array(vec) => {
let arr: &mut Vec<Value> = &mut vec![];
for jval in vec {
arr.push(json_to_value(jval, span)?);
}
Value::list(arr.to_vec(), span)
}
nu_json::Value::Object(val) => {
let mut rec = Record::new();
for (k, v) in val {
let value = json_to_value(v, span)?;
rec.insert(k.clone(), value);
}
Value::record(rec, span)
}
})
}

View File

@ -0,0 +1,9 @@
#[macro_export]
macro_rules! debug_println {
($($arg:tt)*) => {
#[cfg(feature = "debug")]
{
eprintln!($($arg)*);
}
};
}

View File

@ -0,0 +1,3 @@
pub mod json;
pub mod log;

View File

@ -0,0 +1,30 @@
name: Publish Crate
on:
push:
branches:
- main
paths:
- Cargo.toml
release:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Publish to crates.io
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@ -0,0 +1,2 @@
/target
.vscode

2702
nu_plugin_desktop_notifications/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
[dependencies]
nu-plugin = { version = "0.105.2", path = "../nushell/crates/nu-plugin" }
[dependencies.notify-rust]
version = "4.11.7"
[dependencies.nu-protocol]
features = ["plugin"]
version = "0.105.2"
path = "../nushell/crates/nu-protocol"
[package]
authors = ["Motalleb Fallahnezhad <fmotalleb@gmail.com>"]
description = "A nushell plugin to send desktop notifications"
edition = "2024"
homepage = "https://github.com/FMotalleb/nu_plugin_desktop_notifications"
keywords = ["nushell", "desktop", "notification", "plugin"]
license = "MIT"
name = "nu_plugin_desktop_notifications"
readme = "README.md"
repository = "https://github.com/FMotalleb/nu_plugin_desktop_notifications"
version = "1.2.12"

View File

@ -0,0 +1,7 @@
Copyright 2023 "Motalleb Fallahnezhad"
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,93 @@
# 🔔 nu_plugin_desktop_notifications
A [Nushell](https://www.nushell.sh/) plugin for sending desktop notifications using [notify-rust](https://github.com/hoodie/notify-rust).
---
## ✨ Features
- **Send notifications** with custom title, body, icon, and app name.
- **Supports macOS, Windows, and Linux (XDG Desktop)**.
- **Configurable timeout** (for macOS and XDG desktops).
- **Error handling** with optional crash reporting.
---
## 📌 Usage
### **Sending a Notification**
```bash
notify -t "Test notification body" --summary "Test title"
```
### **Flags**
- `-h, --help` → Show help message.
- `-s, --summary <string>` → Title of the notification.
- `-t, --body <string>` → Body message of the notification.
- `--subtitle <string>` → Subtitle (macOS & Windows only).
- `-a, --app-name <string>` → App name for the notification.
- `-i, --icon <filepath>` → Path to an icon for the notification.
- `--timeout <duration>` → Duration before the notification disappears _(macOS & XDG Desktop only)_. Defaults to system settings.
- `--crash-on-error <filepath>` → Return an error if the notification fails.
---
## 🎯 Example: Notify on Task Completion
Send a notification after a task completes, displaying the elapsed time:
![image](https://github.com/FMotalleb/nu_plugin_desktop_notifications/assets/30149519/a4fbc2a9-6537-4d18-8d98-e55ebcd6b0bd)
```bash
def "notify on done" [
task: closure
] {
let start = date now
let result = do $task
let end = date now
let total = $end - $start | format duration sec
let body = $"Task completed in ($total)"
notify -s "Task Finished" -t $body
return $result
}
notify on done { port scan 8.8.8.8 53 }
```
---
## 🔧 Installation
### 🚀 Recommended: Using [nupm](https://github.com/nushell/nupm)
```bash
git clone https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
nupm install --path nu_plugin_desktop_notifications -f
```
### 🛠️ Manual Compilation
```bash
git clone https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
cd nu_plugin_desktop_notifications
cargo build -r
register target/release/nu_plugin_desktop_notifications
```
### 📦 Install via Cargo (using git)
```bash
cargo install --git https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
register ~/.cargo/bin/nu_plugin_desktop_notifications
```
### 📦 Install via Cargo (crates.io) _Not Recommended_
>
> _Since I live in Iran and crates.io often restricts package updates, the version there might be outdated._
```bash
cargo install nu_plugin_desktop_notifications
register ~/.cargo/bin/nu_plugin_desktop_notifications
```

View File

@ -0,0 +1,17 @@
use std log
def main [package_file: path = nupm.nuon] {
let repo_root = (ls -f $package_file | first | get name | path dirname)
let install_root = $env.NUPM_HOME | path join "plugins"
let name = open ($repo_root | path join "Cargo.toml") | get package.name
let features = []
let cmd = $"cargo install --path ($repo_root) --root ($install_root) --features=($features | str join ",")"
log info $"building plugin using: (ansi blue)($cmd)(ansi reset)"
nu -c $cmd
let ext: string = if ($nu.os-info.name == 'windows') { '.exe' } else { '' }
plugin add $"($install_root | path join "bin" $name)($ext)"
log info "do not forget to restart Nushell for the plugin to be fully available!"
}

View File

@ -0,0 +1,7 @@
{
"name": "nu_plugin_desktop_notifications",
"version": "1.2.11",
"description": "A nushell plugin to send desktop notifications",
"license": "LICENSE",
"type": "custom"
}

View File

@ -0,0 +1,3 @@
[toolchain]
profile = "default"
channel = "stable"

View File

@ -0,0 +1,19 @@
use nu_plugin::{serve_plugin, Plugin};
use crate::notify::NotifyCommand;
mod notify;
pub struct NotifyPlugin;
impl Plugin for NotifyPlugin {
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
vec![Box::new(NotifyCommand::new())]
}
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
}
fn main() {
serve_plugin(&mut NotifyPlugin {}, nu_plugin::MsgPackSerializer {})
}

View File

@ -0,0 +1,132 @@
use std::time::Duration;
use notify_rust::{Notification, Timeout};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Value};
use crate::NotifyPlugin;
#[derive(Default)]
pub struct NotifyCommand;
impl NotifyCommand {
pub(crate) fn new() -> NotifyCommand {
NotifyCommand {}
}
pub fn load_string(call: &EvaluatedCall, name: &str) -> Option<String> {
let value = call.get_flag_value(name);
match value {
Some(Value::String { val, .. }) => Some(val),
_ => None,
}
}
}
impl SimplePluginCommand for NotifyCommand {
type Plugin = NotifyPlugin;
fn name(&self) -> &str {
"notify"
}
fn signature(&self) -> Signature {
Signature::build("notify")
.named(
"summary",
SyntaxShape::String,
"summary of the notification",
Some('s'),
)
.named(
"body",
SyntaxShape::String,
"body of the notification",
Some('t'),
)
.named(
"subtitle",
SyntaxShape::String,
"subtitle of the notification [macOS only]",
None,
)
.named(
"app-name",
SyntaxShape::String,
"app name of the notification",
Some('a'),
)
.named(
"icon",
SyntaxShape::Filepath,
"path to the icon of the notification",
Some('i'),
)
.named(
"timeout",
SyntaxShape::Duration,
"duration of the notification [XDG Desktops only] (defaults to system default)",
None,
)
.named(
"crash-on-error",
SyntaxShape::Filepath,
"returns notification error if encountered",
None,
)
.category(Category::Experimental)
}
fn description(&self) -> &str {
"Send a desktop notification with customizable parameters."
}
fn run(
&self,
_plugin: &Self::Plugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
) -> Result<Value, LabeledError> {
let mut notification = Notification::new();
if let Some(summary) = Self::load_string(call, "summary") {
notification.summary(&summary);
}
if let Some(body) = Self::load_string(call, "body") {
notification.body(&body);
}
if let Some(subtitle) = Self::load_string(call, "subtitle") {
notification.subtitle(&subtitle);
}
if let Some(app_name) = Self::load_string(call, "app-name") {
notification.appname(&app_name);
}
if let Some(icon) = Self::load_string(call, "icon") {
notification.icon(&icon);
} else {
notification.auto_icon();
}
if let Some(duration_value) = call.get_flag_value("timeout") {
match duration_value.as_duration() {
Ok(timeout) => {
if let Ok(nanos) = timeout.try_into() {
let duration = Timeout::from(Duration::from_nanos(nanos));
notification.timeout(duration);
}
}
Err(_) => {}
}
}
match notification.show() {
Ok(_) => Ok(input.clone()),
Err(err) => {
if let Ok(true) = call.has_flag("crash-on-error") {
return Err(LabeledError::new(err.to_string())
.with_label("Notification Exception", call.head));
}
Ok(input.clone())
}
}
}
}

1
nu_plugin_hashes/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2524
nu_plugin_hashes/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
[package]
name = "nu_plugin_hashes"
description = "A Nushell plugin that adds 63 cryptographic hash functions from Hashes project"
keywords = ["nu", "plugin", "hash"]
categories = ["algorithms"]
repository = "https://github.com/ArmoredPony/nu_plugin_hashes"
license = "MIT"
version = "0.1.8"
edition = "2024"
[features]
default = [
"ascon-hash",
"belt-hash",
"blake2",
"blake3",
"fsb",
"gost94",
"groestl",
"jh",
"md2",
"md4",
"ripemd",
"sha1",
"sha2",
"sha3",
"shabal",
"skein",
"sm3",
"streebog",
"tiger",
"whirlpool",
]
[dependencies]
nu-cmd-base = { version = "0.105.2", path = "../nushell/crates/nu-cmd-base" }
nu-plugin = { version = "0.105.2", path = "../nushell/crates/nu-plugin" }
nu-protocol = { version = "0.105.2", path = "../nushell/crates/nu-protocol" }
digest = "0.10.7"
ascon-hash = { version = "0.2.0", optional = true }
belt-hash = { version = "0.1.1", optional = true }
blake2 = { version = "0.10.6", optional = true }
blake3 = { version = "1.6.1", optional = true, default-features = false, features = [
"std",
"traits-preview",
] }
fsb = { version = "0.1.3", optional = true }
gost94 = { version = "0.10.4", optional = true }
groestl = { version = "0.10.1", optional = true }
jh = { version = "0.1.0", optional = true }
md2 = { version = "0.10.2", optional = true }
md4 = { version = "0.10.2", optional = true }
ripemd = { version = "0.1.3", optional = true }
sha1 = { version = "0.10.6", optional = true }
sha2 = { version = "0.10.9", optional = true }
sha3 = { version = "0.10.8", optional = true }
shabal = { version = "0.4.1", optional = true }
skein = { version = "0.1.0", optional = true }
sm3 = { version = "0.4.2", optional = true }
streebog = { version = "0.10.2", optional = true }
tiger = { version = "0.2.1", optional = true }
whirlpool = { version = "0.10.4", optional = true }
[build-dependencies]
digest = "0.10.7"
ascon-hash = { version = "0.2.0", optional = true }
belt-hash = { version = "0.1.1", optional = true }
blake2 = { version = "0.10.6", optional = true }
blake3 = { version = "1.6.1", optional = true, default-features = false, features = [
"std",
"traits-preview",
] }
fsb = { version = "0.1.3", optional = true }
gost94 = { version = "0.10.4", optional = true }
groestl = { version = "0.10.1", optional = true }
jh = { version = "0.1.0", optional = true }
md2 = { version = "0.10.2", optional = true }
md4 = { version = "0.10.2", optional = true }
ripemd = { version = "0.1.3", optional = true }
sha1 = { version = "0.10.6", optional = true }
sha2 = { version = "0.10.8", optional = true }
sha3 = { version = "0.10.8", optional = true }
shabal = { version = "0.4.1", optional = true }
skein = { version = "0.1.0", optional = true }
sm3 = { version = "0.4.2", optional = true }
streebog = { version = "0.10.2", optional = true }
tiger = { version = "0.2.1", optional = true }
whirlpool = { version = "0.10.4", optional = true }
[dev-dependencies]
nu-plugin-test-support = { version = "0.105.2", path = "../nushell/crates/nu-plugin-test-support" }
[profile.release]
strip = true
lto = true
codegen-units = 1

7
nu_plugin_hashes/LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2024 ArmoredPony
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

126
nu_plugin_hashes/README.md Normal file
View File

@ -0,0 +1,126 @@
# Hashes for Nushell
A [Nushell](https://www.nushell.sh) plugin that adds a collection of **63**
cryptographic hash functions from [Hashes](https://github.com/RustCrypto/hashes)
project.
This plugin's implementation is based on code stolen from the official Nushell
repository and on compile-time code generation with a build script.
Excess algorithms can be filtered off by selecting only specific features of the
crate.
## Installation
To install this plugin with all algorithms available run
```nu
cargo install nu_plugin_hashes
plugin add ($env.CARGO_HOME ++ /bin/nu_plugin_hashes)
```
or on Windows
```nu
cargo install nu_plugin_hashes
plugin add ($env.CARGO_HOME ++ /bin/nu_plugin_hashes.exe)
```
After loading the plugin, execute `help hash` to list newly added commands
```nu
~> help hash
Apply hash function.
...
Subcommands:
hash ascon - Hash a value using the ascon hash algorithm.
hash ascon-a - Hash a value using the ascon-a hash algorithm.
hash belt - Hash a value using the belt hash algorithm.
hash blake2b-512 - Hash a value using the blake2b-512 hash algorithm.
hash blake2s-256 - Hash a value using the blake2s-256 hash algorithm.
hash fsb160 - Hash a value using the fsb160 hash algorithm.
hash fsb224 - Hash a value using the fsb224 hash algorithm.
...
```
## Features
If you only need some algorithms, disable default features and select only
those you need
```nu
cargo install nu_plugin_hashes --no-default-features --features sha2,streebog
```
Then check what's installed
```nu
~> help hash
Apply hash function.
...
Subcommands:
hash md5 - Hash a value using the md5 hash algorithm.
hash sha224 - Hash a value using the sha224 hash algorithm.
hash sha256 - Hash a value using the sha256 hash algorithm.
hash sha384 - Hash a value using the sha384 hash algorithm.
hash sha512 - Hash a value using the sha512 hash algorithm.
hash sha512-224 - Hash a value using the sha512-224 hash algorithm.
hash sha512-256 - Hash a value using the sha512-256 hash algorithm.
hash streebog256 - Hash a value using the streebog256 hash algorithm.
hash streebog512 - Hash a value using the streebog512 hash algorithm.
Flags:
...
```
## Hashes
The list of implemented algorithms provided by the plugin can be found
in the Hashes project [repository](https://github.com/RustCrypto/hashes).
Almost all algorithms from this project are included in this plugin. The
exceptions are MD5 and SHA-256 as they are already present in Nushell, and
those algorithms, that don't implement the `Digest` trait or require additional
arguments for them to be run.
If you want to import only particular algorithms, build this plugin with those
features, which names correspond to the crates, that supply the algorithms you
want. Consult this [table](https://github.com/RustCrypto/hashes?tab=readme-ov-file#supported-algorithms)
to match features with required algorithms.
If you disable the default features and forget to enable at least one of them,
the plugin won't compile.
## Implemetation details
All the functions are implemented via generic code that I borrowed from Nushell
source file [generic_digest.rs](https://github.com/nushell/nushell/blob/0.101.0/crates/nu-command/src/hash/generic_digest.rs).
Help page for each command is generated with a [build script](./build.rs).
Hashes of the test text for each example are generated during compilation by
this script: the test text is fed to each hashing algorithm, and resulting bytes
are inserted into examples. This approach isdifferent from Nushell's
implementations, where examples are hardcoded as strings. Then, generated
examples are tested with [nu-plugin-test-support](https://crates.io/crates/nu-plugin-test-support)
crate. This ensures that the code in examples always behaves exactly as printed.
## TODO
Here is a list of things that could not be implemented with code generation
as they don't implement the `Digest` trait or require additional arguments
to be executed. I doubt that I will ever complete this list so you are welcome
to contribute.
- [ ] [blake2b] algorithm with runtime defined output size
- [ ] [sha1] algorithm with collision detection
## License
This crate is licensed under [MIT license](LICENSE).
---
<h6>I only created this plugin to explore compile-time code generation and
educate myself on subject of features. The product of my activity terrifies
me and I'm surprised it worked out at all.</h6>
[blake2b]: https://github.com/RustCrypto/hashes/blob/1dbb9535207176fceb93a8ec1d450712714aedec/blake2/src/lib.rs#L67
[sha1]: https://github.com/RustCrypto/hashes/tree/master/sha1-checked

675
nu_plugin_hashes/build.rs Normal file
View File

@ -0,0 +1,675 @@
use digest::DynDigest;
const TEST_TEXT: &str = "abcdefghijklmnopqrstuvwxyz";
struct GeneratedHasherImplMeta {
crate_name: &'static str,
hasher_type_name: &'static str,
hasher_command: &'static str,
hasher: Box<dyn DynDigest>,
}
#[cfg(any(
feature = "ascon-hash",
feature = "belt-hash",
feature = "blake2",
feature = "blake3",
feature = "fsb",
feature = "gost94",
feature = "groestl",
feature = "jh",
feature = "md2",
feature = "md4",
feature = "ripemd",
feature = "sha1",
feature = "sha2",
feature = "sha3",
feature = "shabal",
feature = "skein",
feature = "sm3",
feature = "streebog",
feature = "tiger",
feature = "whirlpool",
))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::{env, io::Write, path::Path};
// MD5 and SHA256 are skipped on purpose
let hasher_impls: Vec<GeneratedHasherImplMeta> = vec![
#[cfg(feature = "ascon-hash")]
GeneratedHasherImplMeta {
crate_name: "ascon_hash",
hasher_type_name: "AsconHash",
hasher_command: "ascon",
hasher: Box::new(ascon_hash::AsconHash::default()),
},
#[cfg(feature = "ascon-hash")]
GeneratedHasherImplMeta {
crate_name: "ascon_hash",
hasher_type_name: "AsconAHash",
hasher_command: "ascon-a",
hasher: Box::new(ascon_hash::AsconAHash::default()),
},
#[cfg(feature = "belt-hash")]
GeneratedHasherImplMeta {
crate_name: "belt_hash",
hasher_type_name: "BeltHash",
hasher_command: "belt",
hasher: Box::new(belt_hash::BeltHash::default()),
},
#[cfg(feature = "blake2")]
GeneratedHasherImplMeta {
crate_name: "blake2",
hasher_type_name: "Blake2s256",
hasher_command: "blake2s-256",
hasher: Box::new(blake2::Blake2s256::default()),
},
#[cfg(feature = "blake2")]
GeneratedHasherImplMeta {
crate_name: "blake2",
hasher_type_name: "Blake2b512",
hasher_command: "blake2b-512",
hasher: Box::new(blake2::Blake2b512::default()),
},
#[cfg(feature = "blake3")]
GeneratedHasherImplMeta {
crate_name: "blake3",
hasher_type_name: "Hasher",
hasher_command: "blake3",
hasher: Box::new(blake3::Hasher::new()),
},
#[cfg(feature = "fsb")]
GeneratedHasherImplMeta {
crate_name: "fsb",
hasher_type_name: "Fsb160",
hasher_command: "fsb160",
hasher: Box::new(fsb::Fsb160::default()),
},
#[cfg(feature = "fsb")]
GeneratedHasherImplMeta {
crate_name: "fsb",
hasher_type_name: "Fsb224",
hasher_command: "fsb224",
hasher: Box::new(fsb::Fsb224::default()),
},
#[cfg(feature = "fsb")]
GeneratedHasherImplMeta {
crate_name: "fsb",
hasher_type_name: "Fsb256",
hasher_command: "fsb256",
hasher: Box::new(fsb::Fsb256::default()),
},
#[cfg(feature = "fsb")]
GeneratedHasherImplMeta {
crate_name: "fsb",
hasher_type_name: "Fsb384",
hasher_command: "fsb384",
hasher: Box::new(fsb::Fsb384::default()),
},
#[cfg(feature = "fsb")]
GeneratedHasherImplMeta {
crate_name: "fsb",
hasher_type_name: "Fsb512",
hasher_command: "fsb512",
hasher: Box::new(fsb::Fsb512::default()),
},
#[cfg(feature = "gost94")]
GeneratedHasherImplMeta {
crate_name: "gost94",
hasher_type_name: "Gost94CryptoPro",
hasher_command: "gost94-crypto-pro",
hasher: Box::new(gost94::Gost94CryptoPro::default()),
},
#[cfg(feature = "gost94")]
GeneratedHasherImplMeta {
crate_name: "gost94",
hasher_type_name: "Gost94UA",
hasher_command: "gost94-ua",
hasher: Box::new(gost94::Gost94UA::default()),
},
#[cfg(feature = "gost94")]
GeneratedHasherImplMeta {
crate_name: "gost94",
hasher_type_name: "Gost94s2015",
hasher_command: "gost94-2015",
hasher: Box::new(gost94::Gost94s2015::default()),
},
#[cfg(feature = "groestl")]
GeneratedHasherImplMeta {
crate_name: "groestl",
hasher_type_name: "Groestl224",
hasher_command: "groestl224",
hasher: Box::new(groestl::Groestl224::default()),
},
#[cfg(feature = "groestl")]
GeneratedHasherImplMeta {
crate_name: "groestl",
hasher_type_name: "Groestl256",
hasher_command: "groestl256",
hasher: Box::new(groestl::Groestl256::default()),
},
#[cfg(feature = "groestl")]
GeneratedHasherImplMeta {
crate_name: "groestl",
hasher_type_name: "Groestl384",
hasher_command: "groestl384",
hasher: Box::new(groestl::Groestl384::default()),
},
#[cfg(feature = "groestl")]
GeneratedHasherImplMeta {
crate_name: "groestl",
hasher_type_name: "Groestl512",
hasher_command: "groestl512",
hasher: Box::new(groestl::Groestl512::default()),
},
#[cfg(feature = "jh")]
GeneratedHasherImplMeta {
crate_name: "jh",
hasher_type_name: "Jh224",
hasher_command: "jh224",
hasher: Box::new(jh::Jh224::default()),
},
#[cfg(feature = "jh")]
GeneratedHasherImplMeta {
crate_name: "jh",
hasher_type_name: "Jh256",
hasher_command: "jh256",
hasher: Box::new(jh::Jh256::default()),
},
#[cfg(feature = "jh")]
GeneratedHasherImplMeta {
crate_name: "jh",
hasher_type_name: "Jh384",
hasher_command: "jh384",
hasher: Box::new(jh::Jh384::default()),
},
#[cfg(feature = "jh")]
GeneratedHasherImplMeta {
crate_name: "jh",
hasher_type_name: "Jh512",
hasher_command: "jh512",
hasher: Box::new(jh::Jh512::default()),
},
#[cfg(feature = "md2")]
GeneratedHasherImplMeta {
crate_name: "md2",
hasher_type_name: "Md2",
hasher_command: "md2",
hasher: Box::new(md2::Md2::default()),
},
#[cfg(feature = "md4")]
GeneratedHasherImplMeta {
crate_name: "md4",
hasher_type_name: "Md4",
hasher_command: "md4",
hasher: Box::new(md4::Md4::default()),
},
#[cfg(feature = "ripemd")]
GeneratedHasherImplMeta {
crate_name: "ripemd",
hasher_type_name: "Ripemd128",
hasher_command: "ripemd128",
hasher: Box::new(ripemd::Ripemd128::default()),
},
#[cfg(feature = "ripemd")]
GeneratedHasherImplMeta {
crate_name: "ripemd",
hasher_type_name: "Ripemd160",
hasher_command: "ripemd160",
hasher: Box::new(ripemd::Ripemd160::default()),
},
#[cfg(feature = "ripemd")]
GeneratedHasherImplMeta {
crate_name: "ripemd",
hasher_type_name: "Ripemd256",
hasher_command: "ripemd256",
hasher: Box::new(ripemd::Ripemd256::default()),
},
#[cfg(feature = "ripemd")]
GeneratedHasherImplMeta {
crate_name: "ripemd",
hasher_type_name: "Ripemd320",
hasher_command: "ripemd320",
hasher: Box::new(ripemd::Ripemd320::default()),
},
#[cfg(feature = "sha1")]
GeneratedHasherImplMeta {
crate_name: "sha1",
hasher_type_name: "Sha1",
hasher_command: "sha1",
hasher: Box::new(sha1::Sha1::default()),
},
#[cfg(feature = "sha2")]
GeneratedHasherImplMeta {
crate_name: "sha2",
hasher_type_name: "Sha224",
hasher_command: "sha224",
hasher: Box::new(sha2::Sha224::default()),
},
#[cfg(feature = "sha2")]
GeneratedHasherImplMeta {
crate_name: "sha2",
hasher_type_name: "Sha384",
hasher_command: "sha384",
hasher: Box::new(sha2::Sha384::default()),
},
#[cfg(feature = "sha2")]
GeneratedHasherImplMeta {
crate_name: "sha2",
hasher_type_name: "Sha512",
hasher_command: "sha512",
hasher: Box::new(sha2::Sha512::default()),
},
#[cfg(feature = "sha2")]
GeneratedHasherImplMeta {
crate_name: "sha2",
hasher_type_name: "Sha512_224",
hasher_command: "sha512-224",
hasher: Box::new(sha2::Sha512_224::default()),
},
#[cfg(feature = "sha2")]
GeneratedHasherImplMeta {
crate_name: "sha2",
hasher_type_name: "Sha512_256",
hasher_command: "sha512-256",
hasher: Box::new(sha2::Sha512_256::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Sha3_224",
hasher_command: "sha3-224",
hasher: Box::new(sha3::Sha3_224::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Sha3_256",
hasher_command: "sha3-256",
hasher: Box::new(sha3::Sha3_256::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Sha3_384",
hasher_command: "sha3-384",
hasher: Box::new(sha3::Sha3_384::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Sha3_512",
hasher_command: "sha3-512",
hasher: Box::new(sha3::Sha3_512::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Keccak224",
hasher_command: "keccak224",
hasher: Box::new(sha3::Keccak224::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Keccak256",
hasher_command: "keccak256",
hasher: Box::new(sha3::Keccak256::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Keccak384",
hasher_command: "keccak384",
hasher: Box::new(sha3::Keccak384::default()),
},
#[cfg(feature = "sha3")]
GeneratedHasherImplMeta {
crate_name: "sha3",
hasher_type_name: "Keccak512",
hasher_command: "keccak512",
hasher: Box::new(sha3::Keccak512::default()),
},
#[cfg(feature = "shabal")]
GeneratedHasherImplMeta {
crate_name: "shabal",
hasher_type_name: "Shabal192",
hasher_command: "shabal192",
hasher: Box::new(shabal::Shabal192::default()),
},
#[cfg(feature = "shabal")]
GeneratedHasherImplMeta {
crate_name: "shabal",
hasher_type_name: "Shabal224",
hasher_command: "shabal224",
hasher: Box::new(shabal::Shabal224::default()),
},
#[cfg(feature = "shabal")]
GeneratedHasherImplMeta {
crate_name: "shabal",
hasher_type_name: "Shabal256",
hasher_command: "shabal256",
hasher: Box::new(shabal::Shabal256::default()),
},
#[cfg(feature = "shabal")]
GeneratedHasherImplMeta {
crate_name: "shabal",
hasher_type_name: "Shabal384",
hasher_command: "shabal384",
hasher: Box::new(shabal::Shabal384::default()),
},
#[cfg(feature = "shabal")]
GeneratedHasherImplMeta {
crate_name: "shabal",
hasher_type_name: "Shabal512",
hasher_command: "shabal512",
hasher: Box::new(shabal::Shabal512::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein256::<skein::consts::U32>",
hasher_command: "skein256-32",
hasher: Box::new(skein::Skein256::<skein::consts::U32>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein256::<skein::consts::U64>",
hasher_command: "skein256-64",
hasher: Box::new(skein::Skein256::<skein::consts::U64>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein256::<skein::consts::U128>",
hasher_command: "skein256-128",
hasher: Box::new(skein::Skein256::<skein::consts::U128>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein512::<skein::consts::U32>",
hasher_command: "skein512-32",
hasher: Box::new(skein::Skein512::<skein::consts::U32>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein512::<skein::consts::U64>",
hasher_command: "skein512-64",
hasher: Box::new(skein::Skein512::<skein::consts::U64>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein512::<skein::consts::U128>",
hasher_command: "skein512-128",
hasher: Box::new(skein::Skein512::<skein::consts::U128>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein1024::<skein::consts::U32>",
hasher_command: "skein1024-32",
hasher: Box::new(skein::Skein1024::<skein::consts::U32>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein1024::<skein::consts::U64>",
hasher_command: "skein1024-64",
hasher: Box::new(skein::Skein1024::<skein::consts::U64>::default()),
},
#[cfg(feature = "skein")]
GeneratedHasherImplMeta {
crate_name: "skein",
hasher_type_name: "Skein1024::<skein::consts::U128>",
hasher_command: "skein1024-128",
hasher: Box::new(skein::Skein1024::<skein::consts::U128>::default()),
},
#[cfg(feature = "sm3")]
GeneratedHasherImplMeta {
crate_name: "sm3",
hasher_type_name: "Sm3",
hasher_command: "sm3",
hasher: Box::new(sm3::Sm3::default()),
},
#[cfg(feature = "streebog")]
GeneratedHasherImplMeta {
crate_name: "streebog",
hasher_type_name: "Streebog256",
hasher_command: "streebog256",
hasher: Box::new(streebog::Streebog256::default()),
},
#[cfg(feature = "streebog")]
GeneratedHasherImplMeta {
crate_name: "streebog",
hasher_type_name: "Streebog512",
hasher_command: "streebog512",
hasher: Box::new(streebog::Streebog512::default()),
},
#[cfg(feature = "tiger")]
GeneratedHasherImplMeta {
crate_name: "tiger",
hasher_type_name: "Tiger",
hasher_command: "tiger",
hasher: Box::new(tiger::Tiger::default()),
},
#[cfg(feature = "tiger")]
GeneratedHasherImplMeta {
crate_name: "tiger",
hasher_type_name: "Tiger2",
hasher_command: "tiger2",
hasher: Box::new(tiger::Tiger2::default()),
},
#[cfg(feature = "whirlpool")]
GeneratedHasherImplMeta {
crate_name: "whirlpool",
hasher_type_name: "Whirlpool",
hasher_command: "whirlpool",
hasher: Box::new(whirlpool::Whirlpool::default()),
},
];
let out_dir = env::var_os("OUT_DIR").unwrap();
let hashers_generated_path = Path::new(&out_dir).join("hashers_generated.rs");
let commands_generated_path =
Path::new(&out_dir).join("commands_generated.rs");
let mut hashers_generated_file =
std::fs::File::create(hashers_generated_path).unwrap();
let mut commands_generated_file =
std::fs::File::create(commands_generated_path).unwrap();
write!(
hashers_generated_file,
"use nu_protocol::{{Example, Span, Value, ShellError}};
use nu_plugin::PluginCommand;
use crate::HashesPlugin;
use crate::hasher::{{Hasher, GenericHasher}};
"
)?;
write!(
commands_generated_file,
"use nu_plugin::PluginCommand;
use crate::{{HashesPlugin, hasher::GenericHasher}};
pub fn commands() -> Vec<Box<dyn PluginCommand<Plugin = HashesPlugin>>> {{
vec![
"
)?;
for mut hasher_impl_meta in hasher_impls {
let feature_name =
hasher_impl_meta.crate_name.replace("-", "_").to_uppercase();
if std::env::var(format!("CARGO_FEATURE_{feature_name}")).is_ok() {
hashers_generated_file
.write_all(build_impl_str(&mut hasher_impl_meta).as_bytes())?;
hashers_generated_file
.write_all(build_test_str(&hasher_impl_meta).as_bytes())?;
let crate_name = hasher_impl_meta.crate_name;
let hasher_type_name = hasher_impl_meta.hasher_type_name;
writeln!(
commands_generated_file,
" Box::new(GenericHasher::<{crate_name}::{hasher_type_name}>::default()),"
)?;
}
}
write!(
commands_generated_file,
" ]
}}"
)?;
hashers_generated_file.flush()?;
commands_generated_file.flush()?;
Ok(())
}
#[cfg(any(
feature = "ascon-hash",
feature = "belt-hash",
feature = "blake2",
feature = "blake3",
feature = "fsb",
feature = "gost94",
feature = "groestl",
feature = "jh",
feature = "md2",
feature = "md4",
feature = "ripemd",
feature = "sha1",
feature = "sha2",
feature = "sha3",
feature = "shabal",
feature = "skein",
feature = "sm3",
feature = "streebog",
feature = "tiger",
feature = "whirlpool",
))]
fn build_impl_str(meta: &mut GeneratedHasherImplMeta) -> String {
let crate_name = meta.crate_name;
let hasher_type_name = meta.hasher_type_name;
let command = meta.hasher_command;
let mut hasher = meta.hasher.clone();
hasher.update(TEST_TEXT.as_bytes());
let hash = hasher.clone().finalize();
format!(
"
impl Hasher for {crate_name}::{hasher_type_name} {{
fn name() -> &'static str {{
\"{command}\"
}}
fn examples() -> Vec<Example<'static>> {{
vec![
Example {{
description: \"Return the {command} hash of a string, hex-encoded\",
example: \"'{TEST_TEXT}' | hash {command}\",
result: Some(Value::string(
\"{}\".to_owned(),
Span::test_data(),
)),
}},
Example {{
description: \"Return the {command} hash of a string, as binary\",
example: \"'{TEST_TEXT}' | hash {command} --binary\",
result: Some(Value::binary(
vec![{}],
Span::test_data(),
)),
}},
Example {{
description: \"Return the {command} hash of a file's contents\",
example: \"open ./nu_0_24_1_windows.zip | hash {command}\",
result: None,
}},
]
}}
}}
",
hash
.iter()
.map(|b| format!("{b:02x?}"))
.collect::<Vec<_>>()
.join(""),
hash
.iter()
.map(|b| format!("0x{b:02x?}"))
.collect::<Vec<_>>()
.join(",")
)
}
#[cfg(any(
feature = "ascon-hash",
feature = "belt-hash",
feature = "blake2",
feature = "blake3",
feature = "fsb",
feature = "gost94",
feature = "groestl",
feature = "jh",
feature = "md2",
feature = "md4",
feature = "ripemd",
feature = "sha1",
feature = "sha2",
feature = "sha3",
feature = "shabal",
feature = "skein",
feature = "sm3",
feature = "streebog",
feature = "tiger",
feature = "whirlpool",
))]
fn build_test_str(meta: &GeneratedHasherImplMeta) -> String {
let crate_name = meta.crate_name;
let hasher_type_name = meta.hasher_type_name;
let command = meta.hasher_command;
let test_name = command.replace("-", "_");
format!(
"
#[test]
fn test_{test_name}_examples() -> Result<(), ShellError> {{
nu_plugin_test_support::PluginTest::new
(
\"hash {command}\",
HashesPlugin.into()
)?
.test_examples(&GenericHasher::<{crate_name}::{hasher_type_name}>::default().examples())
}}
"
)
}
#[cfg(not(any(
feature = "ascon-hash",
feature = "belt-hash",
feature = "blake2",
feature = "blake3",
feature = "fsb",
feature = "gost94",
feature = "groestl",
feature = "jh",
feature = "md2",
feature = "md4",
feature = "ripemd",
feature = "sha1",
feature = "sha2",
feature = "sha3",
feature = "shabal",
feature = "skein",
feature = "sm3",
feature = "streebog",
feature = "tiger",
feature = "whirlpool",
)))]
fn main() {
compile_error!("enable at least one feature to compile this plugin");
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/commands_generated.rs"));

View File

@ -0,0 +1,173 @@
//! Contains a generic trait for hashers that implement `Digest`.
//! This implementation is stolen with minimal changes from *generic_digest.rs*
//! source code file of nushell v0.101.0 which can be found at
//! https://github.com/nushell/nushell/blob/0.101.0/crates/nu-command/src/hash/generic_digest.rs
//! The *hash* module is private, so I had no choice.
use std::{io::Write, marker::PhantomData, ops::Not};
use digest::{Digest, Output};
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
ast::CellPath,
Category,
Example,
IntoPipelineData,
LabeledError,
PipelineData,
ShellError,
Signature,
Span,
SyntaxShape,
Type,
Value,
};
use crate::HashesPlugin;
pub trait Hasher: Digest + Clone {
fn name() -> &'static str;
fn examples() -> Vec<Example<'static>>;
}
#[derive(Clone)]
pub struct GenericHasher<H: Hasher> {
name: String,
description: String,
_hasher: PhantomData<H>,
}
impl<H: Hasher> Default for GenericHasher<H> {
fn default() -> Self {
Self {
name: format!("hash {}", H::name()),
description: format!(
"Hash a value using the {} hash algorithm.",
H::name()
),
_hasher: PhantomData,
}
}
}
struct Arguments {
cell_paths: Option<Vec<CellPath>>,
binary: bool,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
impl<H> PluginCommand for GenericHasher<H>
where
H: Hasher + Write + Send + Sync + 'static,
Output<H>: core::fmt::LowerHex,
{
type Plugin = HashesPlugin;
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Hash)
.input_output_types(vec![
(Type::Binary, Type::Any),
(Type::String, Type::Any),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.switch(
"binary",
"Output binary instead of hexadecimal representation",
Some('b'),
)
.rest(
"rest",
SyntaxShape::CellPath,
format!("Optionally {} hash data by cell path.", H::name()),
)
}
fn description(&self) -> &str {
&self.description
}
fn examples(&self) -> Vec<Example> {
H::examples()
}
fn run(
&self,
_plugin: &HashesPlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let head = call.head;
let binary = call.has_flag("binary")?;
let cell_paths: Vec<CellPath> = call.rest(0)?;
let cell_paths = cell_paths.is_empty().not().then_some(cell_paths);
if let PipelineData::ByteStream(stream, ..) = input {
let mut hasher = H::new();
stream.write_to(&mut hasher)?;
let digest = hasher.finalize();
if binary {
Ok(Value::binary(digest.to_vec(), head).into_pipeline_data())
} else {
Ok(Value::string(format!("{digest:x}"), head).into_pipeline_data())
}
} else {
operate(
action::<H>,
Arguments { binary, cell_paths },
input,
head,
engine.signals(),
)
.map_err(Into::into)
}
}
}
fn action<H>(input: &Value, args: &Arguments, _span: Span) -> Value
where
H: Hasher,
Output<H>: core::fmt::LowerHex,
{
let span = input.span();
let (bytes, span) = match input {
Value::String { val, .. } => (val.as_bytes(), span),
Value::Binary { val, .. } => (val.as_slice(), span),
// Propagate existing errors
Value::Error { .. } => return input.clone(),
other => {
let span = input.span();
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
},
span,
);
}
};
let digest = H::digest(bytes);
if args.binary {
Value::binary(digest.to_vec(), span)
} else {
Value::string(format!("{digest:x}"), span)
}
}

View File

@ -0,0 +1,2 @@
#![allow(unused_imports)]
include!(concat!(env!("OUT_DIR"), "/hashers_generated.rs"));

View File

@ -0,0 +1,17 @@
mod commands_generated;
mod hasher;
mod hashers_generated;
use nu_plugin::Plugin;
pub struct HashesPlugin;
impl Plugin for HashesPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
commands_generated::commands()
}
}

View File

@ -0,0 +1,6 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
use nu_plugin_hashes::HashesPlugin;
fn main() {
serve_plugin(&HashesPlugin, MsgPackSerializer);
}

5
nu_plugin_highlight/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Cargo build output
/target
# JetBrains IDE
/.idea

2376
nu_plugin_highlight/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
[package]
name = "nu_plugin_highlight"
version = "1.4.7+0.105.2"
authors = ["Tim 'Piepmatz' Hesse"]
edition = "2024"
repository = "https://github.com/cptpiepmatz/nu-plugin-highlight"
description = "A nushell plugin for syntax highlighting"
license = "MIT"
keywords = ["nu", "plugin", "syntax", "highlighting"]
categories = ["command-line-utilities", "development-tools", "value-formatting"]
[workspace.dependencies]
# share dependencies with build dependencies
syntect = "5"
bat = { version = "0.24", default-features = false }
[dependencies]
# nu
nu-plugin = { version = "0.105.2", path = "../nushell/crates/nu-plugin" }
nu-protocol = { version = "0.105.2", path = "../nushell/crates/nu-protocol" }
nu-path = { version = "0.105.2", path = "../nushell/crates/nu-path" }
# highlighting
syntect = { workspace = true }
nu-ansi-term = "0.50"
ansi_colours = "1"
bat = { workspace = true }
# guess the type
mime_guess = "2"
[build-dependencies]
patch-apply = "0.8.3"
syntect = { workspace = true }
bat = { workspace = true }

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Tim 'Piepmatz' Hesse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,146 @@
<h1 align="center">nu-plugin-highlight</h1>
<p align="center">
<b>
A <a href="https://www.nushell.sh">nushell</a>
<a href="https://www.nushell.sh/book/plugins.html">plugin</a> for syntax
highlighting.
</b>
</p>
<br>
<div align="center">
[![Version](https://img.shields.io/crates/v/nu-plugin-highlight?style=for-the-badge)](https://crates.io/crates/nu-plugin-highlight)
[![License](https://img.shields.io/crates/l/nu-plugin-highlight?style=for-the-badge)](https://github.com/cptpiepmatz/nu-plugin-highlight/blob/main/LICENSE)
</div>
## About
`nu-plugin-highlight` is a plugin for [Nushell](https://www.nushell.sh) that
provides syntax highlighting for source code.
It uses the [`syntect`](https://crates.io/crates/syntect) library for syntax
highlighting and the [`bat`](https://crates.io/crates/bat) library for easy
access to its ready-to-use assets.
Custom themes can be loaded too.
## Usage
The `highlight` command can be used for syntax highlighting source code.
Here are a few examples:
```nushell
# Highlight a Markdown file by guessing the type from the pipeline metadata
open README.md | highlight
# Highlight a TOML file by its file extension
open Cargo.toml -r | echo $in | highlight toml
# Highlight a Rust file by programming language name
open src/main.rs | echo $in | highlight Rust
# Highlight a bash script by inferring the language (the file should start with a shebang)
open example.sh | echo $in | highlight
# Highlight a TOML file with a different theme
open Cargo.toml -r | highlight -t ansi
# List all available themes
highlight --list-themes
```
### Parameters
- `language <string>`:
This is an optional parameter that can be used to specify the language or file
extension to aid language detection.
### Flags
- `-h, --help`:
Display the help message for the highlight command.
- `-t, --theme <string>`:
The theme used for highlighting.
- `--list-themes`:
List all possible themes.
## Configuration
The plugin can be configured using the
[`$env.config.plugins.highlight`](https://github.com/nushell/nushell/pull/10955)
variable.
### `true_colors`
Enable or disable true colors (24-bit).
By default, this is enabled.
```nushell
$env.config.plugins.highlight.true_colors = true
```
### `theme`
Set a theme to use.
The default theme depends on the operating system.
Use `highlight --list-themes | where default == true` to see your default theme.
Setting this environment variable should allow
`highlight --list-themes | where id == $env.config.plugins.highlight.theme` to
result in a single row with your selected theme.
If you get no results, you have set an invalid theme.
```nushell
$env.config.plugins.highlight.theme = ansi
```
### `custom_themes`
Set a directory to load custom themes from.
Using `synctect`s theme loader, you can load custom themes in the `.tmtheme`
format from a directory that is passed as this configuration value.
```nushell
$env.config.plugins.highlight.custom_themes = ~/.nu/highlight/themes
```
## Plugin Installation
Installing and registering the `nu-plugin-highlight` is a straightforward
process.
Follow these steps:
1. Install the plugin from crates.io using cargo:
```nushell
cargo install nu_plugin_highlight
```
2. Restart your terminal session to ensure the newly installed plugin is recognized.
3. Find path of your installation:
```nushell
which nu_plugin_highlight
```
4. Register the plugin with Nushell:
If you are using a version **lower** than **0.93.0**, use `register` instead of `plugin add`.
```nushell
plugin add path/to/the/plugin/binary
```
5. Make the plugin available for use:
Tip: You can simply restart the shell or terminal. When nushell starts, it loads all plugins.
If you are using a version **lower** than **0.93.0**, you do **not need** to do this.
```nushell
plugin use highlight
```
After registering, the plugin is available as part of your set of commands:
```nushell
help commands | where command_type == "plugin"
```
## Version Numbering
Starting with version `v1.1.0`, the version number of `nu-plugin-highlight`
incorporates the version number of its dependency, `nu-plugin`.
This is denoted in the format `v1.1.0+0.90.1`, where `v1.1.0` refers to the
version of `nu-plugin-highlight` and `0.90.1` refers to the version of the
`nu-plugin` dependency.
## License
`nu_plugin_highlight` is licensed under the MIT License.
See [LICENSE](LICENSE) for more information.

View File

@ -0,0 +1,34 @@
use std::path::PathBuf;
use bat::assets::HighlightingAssets;
use patch_apply::Patch;
use syntect::parsing::SyntaxDefinition;
const NUSHELL_SYNTAX: &str = include_str!("./syntaxes/nushell/nushell.sublime-syntax");
const NUSHELL_PATCH: &str = include_str!("./syntaxes/patches/nushell.sublime-syntax.patch");
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=syntaxes/nushell/nushell.sublime-syntax");
println!("cargo:rerun-if-changed=syntaxes/patches/nushell.sublime-syntax.patch");
println!("cargo:rerun-if-env-changed=OUT_DIR");
let syntax_set = HighlightingAssets::from_binary()
.get_syntax_set()
.unwrap()
.clone();
let mut syntax_set_builder = syntax_set.into_builder();
let patch = Patch::from_single(NUSHELL_PATCH).unwrap();
let syntax = NUSHELL_SYNTAX.to_string();
let syntax = patch_apply::apply(syntax, patch);
let syntax = SyntaxDefinition::load_from_str(&syntax, true, Some("nushell")).unwrap();
syntax_set_builder.add(syntax);
let syntax_set = syntax_set_builder.build();
let out_path = std::env::var("OUT_DIR").unwrap();
let out_path = PathBuf::from(out_path).join("syntax_set.bin");
syntect::dumps::dump_to_uncompressed_file(&syntax_set, out_path).unwrap();
}

View File

@ -0,0 +1,14 @@
unstable_features = true
edition = "2021"
binop_separator = "Back"
control_brace_style = "ClosingNextLine"
format_strings = true
hex_literal_case = "Upper"
imports_granularity = "Module"
overflow_delimited_expr = true
reorder_impl_items = true
reorder_imports = true
group_imports = "StdExternalCrate"
trailing_comma = "Never"
use_field_init_shorthand = true
wrap_comments = true

View File

@ -0,0 +1,158 @@
use std::ops::Deref;
use std::path::Path;
use bat::assets::HighlightingAssets;
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::{SyntaxReference, SyntaxSet};
use syntect::LoadingError;
use crate::terminal;
use crate::theme::{ListThemes, ThemeDescription};
const SYNTAX_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/syntax_set.bin"));
/// The struct that handles the highlighting of code.
pub struct Highlighter {
syntax_set: SyntaxSet,
highlighting_assets: HighlightingAssets,
custom_themes: Option<ThemeSet>
}
impl Highlighter {
/// Creates a new instance of the Highlighter.
pub fn new() -> Self {
Highlighter {
syntax_set: syntect::dumps::from_uncompressed_data(SYNTAX_SET).unwrap(),
highlighting_assets: HighlightingAssets::from_binary(),
custom_themes: None
}
}
pub fn custom_themes_from_folder(
&mut self,
path: impl AsRef<Path>
) -> Result<(), LoadingError> {
let path = nu_path::expand_to_real_path(path);
self.custom_themes = Some(ThemeSet::load_from_folder(path)?);
Ok(())
}
/// Lists all the available themes.
pub fn list_themes(&self, user_default: Option<&str>) -> ListThemes {
let ha = &self.highlighting_assets;
let default_theme_id = user_default.unwrap_or(HighlightingAssets::default_theme());
let mut themes: Vec<_> = ha
.themes()
.map(|t_id| {
let theme = ha.get_theme(t_id);
ThemeDescription {
id: t_id.to_owned(),
name: theme.name.clone(),
author: theme.author.clone(),
default: default_theme_id == t_id
}
})
.collect();
if let Some(custom_themes) = self.custom_themes.as_ref() {
for (id, theme) in custom_themes.themes.iter() {
themes.push(ThemeDescription {
id: id.to_owned(),
name: theme.name.clone(),
author: theme.author.clone(),
default: default_theme_id == id
});
}
}
ListThemes(themes)
}
/// Checks if a given theme id is valid.
pub fn is_valid_theme(&self, theme_name: &str) -> bool {
let ha = &self.highlighting_assets;
let custom_themes = self
.custom_themes
.as_ref()
.map(|themes| themes.themes.keys())
.unwrap_or_default()
.map(Deref::deref);
custom_themes.chain(ha.themes()).any(|t| t == theme_name)
}
/// Highlights the given input text based on the provided language and
/// theme.
pub fn highlight(
&self,
input: &str,
language: Option<&str>,
theme: Option<&str>,
true_colors: bool
) -> String {
let syntax_set = &self.syntax_set;
let syntax_ref: Option<&SyntaxReference> = match language {
Some(language) if !language.is_empty() => {
// allow multiple variants to write the language
let language_lowercase = language.to_lowercase();
let language_capitalized = {
let mut chars = language.chars();
let mut out = String::with_capacity(language.len());
chars
.next()
.expect("language not empty")
.to_uppercase()
.for_each(|c| out.push(c));
chars.for_each(|c| out.push(c));
out
};
syntax_set
.find_syntax_by_name(language)
.or_else(|| syntax_set.find_syntax_by_name(&language_lowercase))
.or_else(|| syntax_set.find_syntax_by_name(&language_capitalized))
.or_else(|| syntax_set.find_syntax_by_extension(language))
.or_else(|| syntax_set.find_syntax_by_extension(&language_lowercase))
.or_else(|| syntax_set.find_syntax_by_extension(&language_capitalized))
}
_ => None
};
let syntax_ref = syntax_ref
.or(syntax_set.find_syntax_by_first_line(input))
.unwrap_or(syntax_set.find_syntax_plain_text());
let theme_id = match theme {
None => HighlightingAssets::default_theme(),
Some(theme) => theme
};
let theme = self
.custom_themes
.as_ref()
.map(|themes| themes.themes.get(theme_id))
.flatten();
let theme = theme.unwrap_or_else(|| self.highlighting_assets.get_theme(theme_id));
let mut highlighter = HighlightLines::new(syntax_ref, theme);
let line_count = input.lines().count();
input
.lines()
.enumerate()
.map(|(i, l)| {
// insert a newline in between lines, this is necessary for bats syntax set
let l = match i == line_count - 1 {
false => format!("{}\n", l.trim_end()),
true => l.trim_end().to_owned()
};
let styled_lines = highlighter.highlight_line(&l, &syntax_set).unwrap();
styled_lines
.iter()
.map(|(style, s)| {
terminal::as_terminal_escaped(*style, s, true_colors, true, false, None)
})
.collect::<String>()
})
.collect::<String>()
}
}

View File

@ -0,0 +1,12 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
use plugin::HighlightPlugin;
mod highlight;
mod plugin;
mod terminal;
mod theme;
/// The main function that serves the plugin using MsgPackSerializer.
fn main() {
serve_plugin(&HighlightPlugin, MsgPackSerializer);
}

View File

@ -0,0 +1,249 @@
use std::path::PathBuf;
use std::str::FromStr;
use mime_guess::Mime;
use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand};
use nu_protocol::shell_error::io::IoError;
use nu_protocol::{
Category, DataSource, ErrorLabel, Example, FromValue, IntoValue, LabeledError, PipelineData,
PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value
};
use syntect::LoadingError;
use crate::highlight::Highlighter;
/// The struct that handles the plugin itself.
pub struct HighlightPlugin;
impl Plugin for HighlightPlugin {
fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
vec![Box::new(Highlight)]
}
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
}
#[derive(Debug, FromValue, Default)]
struct Config {
pub theme: Option<Spanned<String>>,
pub true_colors: Option<bool>,
pub custom_themes: Option<Spanned<PathBuf>>
}
struct Highlight;
impl PluginCommand for Highlight {
type Plugin = HighlightPlugin;
fn name(&self) -> &str {
"highlight"
}
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.optional(
"language",
SyntaxShape::String,
"language or file extension to help language detection"
)
.named(
"theme",
SyntaxShape::String,
"them used for highlighting",
Some('t')
)
.switch("list-themes", "list all possible themes", None)
.category(Category::Strings)
.input_output_type(Type::String, Type::String)
.input_output_type(
Type::Any,
Type::Table(
vec![
(String::from("id"), Type::String),
(String::from("name"), Type::String),
(String::from("author"), Type::String),
(String::from("default"), Type::Bool),
]
.into()
)
)
}
fn description(&self) -> &str {
"Syntax highlight source code."
}
fn run(
&self,
_plugin: &Self::Plugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData
) -> Result<PipelineData, LabeledError> {
let mut highlighter = Highlighter::new();
let config = Option::<Config>::from_value(engine.get_plugin_config()?.unwrap_or_default())?
.unwrap_or_default();
if let Some(custom_themes_path) = config.custom_themes {
match highlighter.custom_themes_from_folder(&custom_themes_path.item) {
Ok(_) => (),
Err(LoadingError::Io(err)) => {
return Err(LabeledError::from(ShellError::from(
IoError::new_with_additional_context(
err,
custom_themes_path.span,
custom_themes_path.item,
"Error while loading custom themes"
)
)))
}
Err(err) => {
return Err(labeled_error(
err,
"Error while loading custom themes",
custom_themes_path.span,
None
))
}
}
}
let theme = call
.get_flag_value("theme")
.map(Spanned::<String>::from_value)
.transpose()?
.or(config.theme);
if let Some(theme) = &theme {
if !highlighter.is_valid_theme(&theme.item) {
return Err(labeled_error(
"use `highlight --list-themes` to list all themes",
format!("Unknown passed theme {:?}", &theme.item),
theme.span,
None
));
}
}
let theme = theme.map(|spanned| spanned.item);
let theme = theme.as_deref();
let true_colors = config.true_colors.unwrap_or(true);
if call.has_flag("list-themes")? {
let themes = highlighter.list_themes(theme).into_value(call.head);
return Ok(PipelineData::Value(themes, None));
}
let metadata = input.metadata();
let input = input.into_value(call.head)?;
let Spanned { item: input, span } = Spanned::<String>::from_value(input)?;
let language = language_hint(call, metadata.as_ref())?;
let highlighted = highlighter.highlight(&input, language.as_deref(), theme, true_colors);
let highlighted = Value::string(highlighted, span);
Ok(PipelineData::Value(highlighted, metadata))
}
fn search_terms(&self) -> Vec<&str> {
vec!["syntax", "highlight", "highlighting"]
}
fn examples(&self) -> Vec<Example> {
const fn example<'e>(description: &'e str, example: &'e str) -> Example<'e>
where
'e: 'static
{
Example {
example,
description,
result: None
}
}
vec![
example(
"Highlight a Markdown file by guessing the type from the pipeline metadata",
"open README.md | highlight"
),
example(
"Highlight a toml file by its file extension",
"open Cargo.toml -r | echo $in | highlight toml"
),
example(
"Highlight a rust file by programming language",
"open src/main.rs | echo $in | highlight Rust"
),
example(
"Highlight a bash script by inferring the language (needs shebang)",
"open example.sh | echo $in | highlight"
),
example(
"Highlight a toml file with another theme",
"open Cargo.toml -r | highlight -t ansi"
),
example("List all available themes", "highlight --list-themes"),
]
}
}
fn language_hint(
call: &EvaluatedCall,
metadata: Option<&PipelineMetadata>
) -> Result<Option<String>, ShellError> {
// first use passed argument
let arg = call.opt(0)?.map(String::from_value).transpose()?;
// then try to parse a mime type
let content_type = || -> Option<String> {
let metadata = metadata?;
let content_type = metadata.content_type.as_ref();
let content_type = content_type?.as_str();
let content_type = Mime::from_str(content_type).ok()?;
let sub_type = content_type.subtype().to_string();
match sub_type.as_str() {
"tab-separated-values" => Some("tsv".to_string()),
"x-toml" => Some("toml".to_string()),
"x-nuscript" | "x-nushell" | "x-nuon" => Some("nushell".to_string()),
s if s.starts_with("x-") => None, // we cannot be sure about this type,
_ => Some(sub_type)
}
};
// as last resort, try to use the extension of data source
let data_source = || -> Option<String> {
let data_source = &metadata?.data_source;
let DataSource::FilePath(path) = data_source
else {
return None;
};
let extension = path.extension()?.to_string_lossy();
Some(extension.to_string())
};
Ok(arg.or_else(content_type).or_else(data_source))
}
/// Simple constructor for [`LabeledError`].
fn labeled_error(
msg: impl ToString,
label: impl ToString,
span: Span,
inner: impl Into<Option<ShellError>>
) -> LabeledError {
LabeledError {
msg: msg.to_string(),
labels: Box::new(vec![ErrorLabel {
text: label.to_string(),
span
}]),
code: None,
url: None,
help: None,
inner: match inner.into() {
Some(inner) => Box::new(vec![inner.into()]),
None => Box::new(vec![])
}
}
}

View File

@ -0,0 +1,87 @@
// pulled from:
// https://github.com/sharkdp/bat/blob/8676bbf97f2832ad2231e102ca9c9b7b72267fda/src/terminal.rs
// applied patch to 64-67 to fix default update construction
#![cfg_attr(rustfmt, rustfmt_skip)]
use nu_ansi_term::Color::{self, Fixed, Rgb};
use nu_ansi_term::{self, Style};
use syntect::highlighting::{self, FontStyle};
pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> Option<nu_ansi_term::Color> {
if color.a == 0 {
// Themes can specify one of the user-configurable terminal colors by
// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set
// to the 8-bit color palette number. The built-in themes ansi, base16,
// and base16-256 use this.
Some(match color.r {
// For the first 8 colors, use the Color enum to produce ANSI escape
// sequences using codes 30-37 (foreground) and 40-47 (background).
// For example, red foreground is \x1b[31m. This works on terminals
// without 256-color support.
0x00 => Color::Black,
0x01 => Color::Red,
0x02 => Color::Green,
0x03 => Color::Yellow,
0x04 => Color::Blue,
0x05 => Color::Purple,
0x06 => Color::Cyan,
0x07 => Color::White,
// For all other colors, use Fixed to produce escape sequences using
// codes 38;5 (foreground) and 48;5 (background). For example,
// bright red foreground is \x1b[38;5;9m. This only works on
// terminals with 256-color support.
//
// TODO: When ansi_term adds support for bright variants using codes
// 90-97 (foreground) and 100-107 (background), we should use those
// for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff.
n => Fixed(n),
})
} else if color.a == 1 {
// Themes can specify the terminal's default foreground/background color
// (i.e. no escape sequence) using the encoding #RRGGBBAA with AA set to
// 01. The built-in theme ansi uses this.
None
} else if true_color {
Some(Rgb(color.r, color.g, color.b))
} else {
Some(Fixed(ansi_colours::ansi256_from_rgb((
color.r, color.g, color.b,
))))
}
}
pub fn as_terminal_escaped(
style: highlighting::Style,
text: &str,
true_color: bool,
colored: bool,
italics: bool,
background_color: Option<highlighting::Color>,
) -> String {
if text.is_empty() {
return text.to_string();
}
let mut style = if !colored {
Style::default()
} else {
let mut color = Style {
foreground: to_ansi_color(style.foreground, true_color),
..Default::default()
};
if style.font_style.contains(FontStyle::BOLD) {
color = color.bold();
}
if style.font_style.contains(FontStyle::UNDERLINE) {
color = color.underline();
}
if italics && style.font_style.contains(FontStyle::ITALIC) {
color = color.italic();
}
color
};
style.background = background_color.and_then(|c| to_ansi_color(c, true_color));
style.paint(text).to_string()
}

View File

@ -0,0 +1,20 @@
use nu_protocol::{IntoValue, Span, Value};
/// Description of a theme.
#[derive(Debug, IntoValue)]
pub struct ThemeDescription {
pub id: String,
pub name: Option<String>,
pub author: Option<String>,
pub default: bool
}
/// List of theme descriptions.
#[derive(Debug)]
pub struct ListThemes(pub Vec<ThemeDescription>);
impl IntoValue for ListThemes {
fn into_value(self, span: Span) -> Value {
self.0.into_value(span)
}
}

View File

@ -0,0 +1,13 @@
{
"clients": {
"nushell": {
"command": [
"/home/kira/.cargo/bin/nu",
"--lsp",
"--no-config-file"
],
"enabled": true,
"selector": "source.nu"
}
}
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 kurokirasama
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,24 @@
# Nushell syntax highlight for sublime text
- Just copied matlab syntax file and modified it for nushell
- Use nushell lsp language server (need lsp sublime package)
## Installation
1. In sublime text install the LSP package.
2. After cloning and cd-ing into the directory, in nushell run (tested in Ubuntu 20.04):
```nu
ls
| find -v README & export & color
| get name
| ansi strip
| each {|file|
cp -f $file (~/.config/sublime-text/Packages/User | path expand)
}
```
3. In sublime, open `Preferences > Customize Color Scheme` and add the contents of `sublime-color-scheme`. Modify to your liking.
## Update commands
If you need to update nushell functions or add your custom commands and aliases, run:
```nu
chmod +x export.nu
./export.nu
```

View File

@ -0,0 +1,58 @@
#update nushell sublime syntax
export def "nushell-syntax-2-sublime" [] {
let builtin = filter-command built-in
let plugins = filter-command plugin
let custom = filter-command custom
let keywords = filter-command keyword
let aliases = (
scope aliases
| get name
| uniq
| str join " | "
)
let personal_external = (
$env.PATH
| find bash & nushell
| get 0
| ansi strip
| ls $in
| find -v Readme
| get name
| path parse
| get stem
| str join " | "
)
let extra_keywords = " | else | catch"
let builtin = " (?x: " + $builtin + ")"
let plugins = " (?x: " + $plugins + ")"
let custom = " (?x: " + $custom + ")"
let keywords = " (?x: " + $keywords + $extra_keywords + ")"
let aliases = " (?x: " + $aliases + ")"
let personal_external = " (?x: " + $personal_external + ")"
let operators = " (?x: and | or | mod | in | not-in | not | xor | bit-or | bit-xor | bit-and | bit-shl | bit-shr | starts-with | ends-with)"
let new_commands = [] ++ $builtin ++ $custom ++ $plugins ++ $keywords ++ $aliases ++ $personal_external ++ $operators
mut file = open ~/.config/sublime-text/Packages/User/nushell.sublime-syntax | lines
let idx = $file | indexify | find '(?x:' | get index | drop | enumerate
for i in $idx {
$file = ($file | upsert $i.item ($new_commands | get $i.index))
}
$file | save -f ~/.config/sublime-text/Packages/User/nushell.sublime-syntax
}
#add a hidden column with the content of the # column
export def indexify [
column_name?: string = 'index'
] {
enumerate
| upsert $column_name {|el|
$el.index
}
| flatten
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>scope</key>
<string>source.nu</string>
<key>settings</key>
<dict>
<key>shellVariables</key>
<array>
<dict>
<key>name</key>
<string>TM_COMMENT_START</string>
<key>value</key>
<string># </string>
</dict>
<dict>
<key>name</key>
<string>TM_COMMENT_START_2</string>
<key>value</key>
<string>
</string>
</dict>
<dict>
<key>name</key>
<string>TM_COMMENT_END_2</string>
<key>value</key>
<string>
</string>
</dict>
</array>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,6 @@
{
"extensions":
[
"nu",
],
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
//add this to your theme.sublime-color-scheme
{
"rules": [
{
// "scope": "string",
// "foreground": "#00FF00",
},
{
"scope": "comment",
"foreground": "#ff0000",
},
{
"scope": "support.function.builtin.nu",
"foreground": "#BB00FF",
},
{
"scope": "support.function.custom.nu",
"foreground": "#FF00FF",
},
{
"scope": "support.function.plugin.nu",
"foreground": "#004CFF",
},
{
"scope": "support.function.keywords.nu",
"foreground": "#A020F0",
},
{
"scope": "support.function.aliases.nu",
"foreground": "#D580FF",
},
{
"scope": "support.function.personal.nu",
"foreground": "#004CFF",
},
{
"scope": "support.function.operators.nu",
"foreground": "#FFFF00",
},
{
"scope": "support.function.boolean.nu",
"foreground": "#00FFFF",
}
],
}

View File

@ -0,0 +1,13 @@
diff --git a/nushell.sublime-syntax b/nushell.sublime-syntax
index b164bbf..c66d6f1 100644
--- a/nushell.sublime-syntax
+++ b/nushell.sublime-syntax
@@ -25,7 +25,7 @@ variables:
(?x: all | ansi | any | append | ast | bits | bytes | cd | char | chunks | clear | collect | columns | commandline | compact | complete | config | cp | date | debug | decode | default | describe | detect | do | drop | du | each | echo | encode | enumerate | error | every | exec | exit | explain | explore | fill | filter | find | first | flatten | fmt | format | from | generate | get | glob | grid | group-by | hash | headers | help | hide-env | histogram | history | http | ignore | input | insert | inspect | interleave | into | is-admin | is-empty | is-not-empty | is-terminal | items | join | keybindings | kill | last | length | let-env | lines | load-env | ls | math | merge | metadata | mkdir | mktemp | move | mv | nu-check | nu-highlight | open | overlay | panic | par-each | parse | path | plugin | port | prepend | print | ps | query | random | range | reduce | reject | rename | reverse | rm | roll | rotate | run-external | save | schema | scope | select | seq | shuffle | skip | sleep | sort | sort-by | split | split-by | start | stor | str | sys | table | take | tee | term | timeit | to | touch | transpose | tutor | ulimit | uname | uniq | update | upsert | url | values | version | view | watch | which | whoami | window | with-env | wrap | zip)
custom_functions: |-
- (?x: 7z | ? | _atuin_search_cmd | activate | adbtasker | add-preamble | ai | ansi-strip-table | apps-update | askai | askdalle | askpdf | autolister | autouse-file | balena | banner | bar | base2dec | batstat | bhe-update | bitly | cblue | cd-pipe | chat_gpt | chatpdf | check-link | claude_ai | clean-analytics | clone-ubuntu-install | code | column | column2 | const-table | copy-downloads-2-ubbdrive | copy-research-2-ubbdrive | copy-scripts-and-commit | copy-webies-2-ubbdrive | copy-yandex-and-commit | coretemp | countdown | cp-pipe | cpwd | create_left_prompt | create_right_prompt | dall_e | date | debunk-table | dec2base | default-table | dpx | echo-c | echo-g | echo-r | exchange_rates | export-nushell-docs | find-file | find-index | fix-docker | fix-green-dirs | format-mails | fuzzy-dispatcher | fuzzy-select-fs | gcal | get-aliases | get-devices | get-dirs | get-env | get-files | get-github-latest | get-input | get-ips | get-keybindings | get-miss-chapters | get-monitors | get-pass | get-phone-number | get-rows | get-vers | get_weather_by_interval | gg | gg-trans | github-app-update | gmail | gnome-settings | gnu-plot | google_ai | google_search | goto-nuconfigdir | grep-nu | group-list | guake | h | habitica | history | history-stats | http | indexify | install-font | intersect | into | is-column | is-in | is-mounted | iselect | isleap | jd | jdown | join-text-files | killn | le | left_prompt | lg | libreoff | list-diff | list-sum | listen-ports | lister | lists2table | ln | lo | ls-ports | lt | m | maps | math | matlab-cli | mcx | med_discord | media | mk-anime | mkcd | monitor | mpv | multiwhere | mv-anime | mv-manga | mv-pipe | mv-torrents | my-pandoc | my-pdflatex | nchat | nerd-fonts-clean | network-switcher | nu-crypt | nu-sloc | nufetch | nushell-syntax-2-sublime | o_llama | obs | op | open-analytics | open-credential | open-link | openf | openl | openm | patch-font | pdf | pip3-upgrade | pivot-table | plot-table | png-plot | print-file | progress_bar | ps | psn | pwd | pwd-short | qrenc | quick-ubuntu-and-tools-update-module | ram | rand-select | randi | random | range2list | rclone | re-enamerate | rebrandly | rename-all | rename-date | rename-file | replicate-tree | reset-alpine-auth | return-error | rm-empty-dirs | rm-pipe | rml | rmount | save-credential | scale-minmax | scale-minmax-table | scompact | send-gmail | set-env | set-screen | setdiff | show-ips | show_banner | speedtest-plot | ssh-sin-pass | ssh-termux | ssh-to | std | stop-net-apps | str | subl | sum-size | supgrade | svg2pdf | sys | t | table-diff | table2record | tasker | tasker-join | to-conversiones | to-onedrive | tokei | token2word | trans | tts | typeof | ubb | ubb_announce | um | umall | union | uniq-by | unset-env | up2ubb | update-nu-config | upload-debs-to-gdrive | usage | ver | verify | weather | wget-all | which-cd | wifi-info | wifi-pass | xls2csv | ydx | yt-api | ytcli | ytm | z | zi)
+ (?x: 7z | \? | _atuin_search_cmd | activate | adbtasker | add-preamble | ai | ansi-strip-table | apps-update | askai | askdalle | askpdf | autolister | autouse-file | balena | banner | bar | base2dec | batstat | bhe-update | bitly | cblue | cd-pipe | chat_gpt | chatpdf | check-link | claude_ai | clean-analytics | clone-ubuntu-install | code | column | column2 | const-table | copy-downloads-2-ubbdrive | copy-research-2-ubbdrive | copy-scripts-and-commit | copy-webies-2-ubbdrive | copy-yandex-and-commit | coretemp | countdown | cp-pipe | cpwd | create_left_prompt | create_right_prompt | dall_e | date | debunk-table | dec2base | default-table | dpx | echo-c | echo-g | echo-r | exchange_rates | export-nushell-docs | find-file | find-index | fix-docker | fix-green-dirs | format-mails | fuzzy-dispatcher | fuzzy-select-fs | gcal | get-aliases | get-devices | get-dirs | get-env | get-files | get-github-latest | get-input | get-ips | get-keybindings | get-miss-chapters | get-monitors | get-pass | get-phone-number | get-rows | get-vers | get_weather_by_interval | gg | gg-trans | github-app-update | gmail | gnome-settings | gnu-plot | google_ai | google_search | goto-nuconfigdir | grep-nu | group-list | guake | h | habitica | history | history-stats | http | indexify | install-font | intersect | into | is-column | is-in | is-mounted | iselect | isleap | jd | jdown | join-text-files | killn | le | left_prompt | lg | libreoff | list-diff | list-sum | listen-ports | lister | lists2table | ln | lo | ls-ports | lt | m | maps | math | matlab-cli | mcx | med_discord | media | mk-anime | mkcd | monitor | mpv | multiwhere | mv-anime | mv-manga | mv-pipe | mv-torrents | my-pandoc | my-pdflatex | nchat | nerd-fonts-clean | network-switcher | nu-crypt | nu-sloc | nufetch | nushell-syntax-2-sublime | o_llama | obs | op | open-analytics | open-credential | open-link | openf | openl | openm | patch-font | pdf | pip3-upgrade | pivot-table | plot-table | png-plot | print-file | progress_bar | ps | psn | pwd | pwd-short | qrenc | quick-ubuntu-and-tools-update-module | ram | rand-select | randi | random | range2list | rclone | re-enamerate | rebrandly | rename-all | rename-date | rename-file | replicate-tree | reset-alpine-auth | return-error | rm-empty-dirs | rm-pipe | rml | rmount | save-credential | scale-minmax | scale-minmax-table | scompact | send-gmail | set-env | set-screen | setdiff | show-ips | show_banner | speedtest-plot | ssh-sin-pass | ssh-termux | ssh-to | std | stop-net-apps | str | subl | sum-size | supgrade | svg2pdf | sys | t | table-diff | table2record | tasker | tasker-join | to-conversiones | to-onedrive | tokei | token2word | trans | tts | typeof | ubb | ubb_announce | um | umall | union | uniq-by | unset-env | up2ubb | update-nu-config | upload-debs-to-gdrive | usage | ver | verify | weather | wget-all | which-cd | wifi-info | wifi-pass | xls2csv | ydx | yt-api | ytcli | ytm | z | zi)
plugin_functions: |-
(?x: from | gstat | highlight | hist | inc | plot | polars | port | query | to | xyplot)

View File

@ -0,0 +1,49 @@
on:
workflow_dispatch:
schedule:
- cron: '0 0 */2 * *'
name: Update dependencies
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Setup Nushell
uses: hustcer/setup-nu@main
with:
version: "*"
- uses: actions/checkout@v2
- name: prepare
shell: nu {0}
run: |
nu -c '
cargo install cargo-edit cargo-upgrades nu_plugin_inc -f
'
- name: Update Dependencies
shell: nu {0}
run: |
nu -c '
register /home/runner/.cargo/bin/nu_plugin_inc
cargo upgrade
let changed = git status -s | is-empty | not $in
if ($changed) {
open Cargo.toml
| upsert package.version ( $in
| get package.version
| inc --patch
)
| save Cargo.toml -f
open package.nuon
| upsert version ( open Cargo.toml | get package.version )
| save package.nuon -f
cargo upgrade
}
echo { "changed": $changed }
'
- uses: EndBug/add-and-commit@v9
with:
author_name: GitHub-Action

View File

@ -0,0 +1,30 @@
name: Publish Crate
on:
push:
branches:
- main
paths:
- Cargo.tom
release:
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Publish to crates.io
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

5
nu_plugin_image/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
.vscode
*.png
*.ansi
*.tmp

8
nu_plugin_image/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
nu_plugin_image/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/nu_plugin_image.iml" filepath="$PROJECT_DIR$/.idea/nu_plugin_image.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
nu_plugin_image/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

3324
nu_plugin_image/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
[dependencies]
slog = "2.7.0"
termcolor = "1.4.1"
ansi_colours = "1.2.3"
crossterm = "0.29.0"
image = "0.25.6"
imageproc = "0.25.0"
include-flate = "0.3.0"
ab_glyph = "0.2.29"
vte = "0.15.0"
lazy_static = "1.5.0"
slog-term = "2.9.1"
slog-async = "2.8.0"
[dependencies.clap]
features = ["derive"]
version = "4.5.40"
[dependencies.nu-plugin]
version = "0.105.2"
path = "../nushell/crates/nu-plugin"
[dependencies.nu-protocol]
features = ["plugin"]
version = "0.105.2"
path = "../nushell/crates/nu-protocol"
[features]
all-fonts = ["font-iosevka_term", "font-anonymous_pro", "font-ubuntu"]
default = []
font-anonymous_pro = []
font-iosevka_term = []
font-ubuntu = []
with-debug = ["slog/max_level_debug", "slog/release_max_level_debug"]
with-trace = ["slog/max_level_trace", "slog/release_max_level_trace"]
[package]
authors = ["Motalleb Fallahnezhad <fmotalleb@gmail.com>"]
description = "A nushell plugin to open png images in the shell and save ansi string as images (like tables or ...)"
edition = "2024"
homepage = "https://github.com/FMotalleb/nu_plugin_image"
keywords = ["nushell", "image", "render", "plugin"]
license = "MIT"
name = "nu_plugin_image"
readme = "README.md"
repository = "https://github.com/FMotalleb/nu_plugin_image"
version = "0.105.1"

7
nu_plugin_image/LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2023 "Motalleb Fallahnezhad"
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

115
nu_plugin_image/README.md Normal file
View File

@ -0,0 +1,115 @@
# 🖼 nu_plugin_image
A [Nushell](https://www.nushell.sh/) plugin to convert ANSI strings into PNG images and create ANSI text from images.
---
## ✨ Features
This plugin allows you to:
- Convert ANSI strings to PNG images with customizable fonts and themes.
- Create ANSI text from an image, enabling you to transform visual data into a textual representation.
---
### **`to png`** Convert ANSI String to PNG Image
The `to png` command converts an ANSI string into a PNG image. Customizable font and theme options are available, with custom flags overriding the default settings.
#### 📌 Usage
```bash
> to png {flags} (output-path)
```
#### ⚙️ Available Flags
- `-h, --help` → Display the help message for this command.
- `-w, --width <int>` → Output width.
- `-t, --theme <string>` → Select the theme of the output. Available themes: ["vscode", "xterm", "ubuntu", "eclipse", "mirc", "putty", "winxp", "terminal", "win10", "win_power-shell", "win_ps"]. Defaults to `vscode`.
- `--font <string>` → Select the font from one of ["SourceCodePro", "Ubuntu", "IosevkaTerm", "AnonymousPro"]. Defaults to the first font in the list.
- `--custom-font-regular <path>` → Path to a custom regular font.
- `--custom-font-bold <path>` → Path to a custom bold font.
- `--custom-font-italic <path>` → Path to a custom italic font.
- `--custom-font-bold_italic <path>` → Path to a custom bold italic font.
- `--custom-theme-fg <string>` → Custom foreground color in hex format (e.g., `#FFFFFF` for white).
- `--custom-theme-bg <string>` → Custom background color in hex format (e.g., `#00000000` for transparent).
- `--custom-theme-black <string>` → Custom black color in hex format (e.g., `#1C1C1C`).
- `--custom-theme-red <string>` → Custom red color in hex format (e.g., `#FF0000`).
- `--custom-theme-green <string>` → Custom green color in hex format (e.g., `#00FF00`).
- `--custom-theme-yellow <string>` → Custom yellow color in hex format (e.g., `#FFFF00`).
- `--custom-theme-blue <string>` → Custom blue color in hex format (e.g., `#0000FF`).
- `--custom-theme-magenta <string>` → Custom magenta color in hex format (e.g., `#FF00FF`).
- `--custom-theme-cyan <string>` → Custom cyan color in hex format (e.g., `#00FFFF`).
- `--custom-theme-white <string>` → Custom white color in hex format (e.g., `#FFFFFF`).
- `--custom-theme-bright_black <string>` → Custom bright black color in hex format (e.g., `#808080`).
- `--custom-theme-bright_red <string>` → Custom bright red color in hex format (e.g., `#FF5555`).
- `--custom-theme-bright_green <string>` → Custom bright green color in hex format (e.g., `#55FF55`).
- `--custom-theme-bright_yellow <string>` → Custom bright yellow color in hex format (e.g., `#FFFF55`).
- `--custom-theme-bright_blue <string>` → Custom bright blue color in hex format (e.g., `#5555FF`).
- `--custom-theme-bright_magenta <string>` → Custom bright magenta color in hex format (e.g., `#FF55FF`).
- `--custom-theme-bright_cyan <string>` → Custom bright cyan color in hex format (e.g., `#55FFFF`).
- `--custom-theme-bright_white <string>` → Custom bright white color in hex format (e.g., `#FFFFFF`).
- `--log-level <string>` → Set log level. Options: `CRITICAL (c)`, `ERROR (e)`, `WARN (w)`, `INFO (i)`, `DEBUG (d)`, `TRACE (t)`. Defaults to `INFO`.
#### 📊 Example: Convert ANSI String to PNG with Custom Theme
```bash
> to png --theme "xterm" --custom-theme-fg "#FF00FF" --custom-theme-bg "#00000000" output.png
```
---
### **`from png`** Create ANSI Text from an Image
The `from png` command converts an image into its corresponding ANSI text representation.
#### 📌 Usage
```bash
> from png {flags}
```
#### ⚙️ Available Flags
- `-h, --help` → Display the help message for this command.
- `-x, --width <int>` → Output width, in characters.
- `-y, --height <int>` → Output height, in characters.
- `--log-level <string>` → Set log level. Options: `CRITICAL (c)`, `ERROR (e)`, `WARN (w)`, `INFO (i)`, `DEBUG (d)`, `TRACE (t)`. Defaults to `INFO`.
#### 📊 Example: Convert PNG Image to ANSI Text
```bash
> from png --width 80 --height 20 image.png
```
---
## 🔧 Installation
### 🚀 Recommended: Using [nupm](https://github.com/nushell/nupm)
This method automatically handles dependencies and features.
```bash
git clone https://github.com/FMotalleb/nu_plugin_image.git
nupm install --path nu_plugin_image -f
```
### 🛠️ Manual Compilation
```bash
git clone https://github.com/FMotalleb/nu_plugin_image.git
cd nu_plugin_image
cargo build -r
plugin add target/release/nu_plugin_image
```
### 📦 Install via Cargo (using git)
```bash
cargo install --git https://github.com/FMotalleb/nu_plugin_image.git
plugin add ~/.cargo/bin/nu_plugin_image
```

45
nu_plugin_image/build.nu Normal file
View File

@ -0,0 +1,45 @@
use std log
# TODO add licenses
let fonts = [
[name, feature];
[
"AnonymousPro Font",
font-anonymous_pro
],
[
"IosevkaTerm Font",
font-iosevka_term
],
[
"Ubuntu Font",
font-ubuntu
],
[
"Debug log level (only used for debuging)",
with-debug
],
[
"Trace log level (only used for advanced debuging)",
with-trace
],
]
def main [package_file: path] {
let repo_root = $package_file | path dirname
let install_root = $env.NUPM_HOME | path join "plugins"
let selected_fonts = $fonts
| input list -d name -m "select features to install"
| get feature
let name = open ($repo_root | path join "Cargo.toml") | get package.name
let ext = if ($nu.os-info.name == 'windows') { '.exe' } else { '' }
let command = $"cargo install --path ($repo_root) --root ($install_root) --features=\"($selected_fonts | str join ',')\""
log info $"building using `($command)`"
nu --commands $"($command)"
plugin add $"($install_root | path join "bin" $name)($ext)"
log info "do not forget to restart Nushell for the plugin to be fully available!"
nu ($repo_root | path join scripts theme_exporter.nu)
}

View File

@ -0,0 +1,7 @@
{
"name": "nu_plugin_image",
"version": "0.104.0",
"description": "A nushell plugin to open png images in the shell and save ansi string as images (like tables or ...)",
"license": "LICENSE",
"type": "custom"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,3 @@
[toolchain]
profile = "default"
channel = "stable"

View File

@ -0,0 +1,78 @@
use std log
def confirm [message: string] : any -> bool {
["yes","no"] | input list $message | $in == "yes"
}
def "from binary" [] : binary -> string {
$in | encode base64 | base64 -d
}
def get-terminal-colors [] {
let colors = (0..15 | each {|i| $"4;($i);?"
| (term query $"(ansi osc)(ansi -o $in)(ansi st)" --terminator "\e\\"
| from binary
| split row :
| get 1
| split row /)
})
let env_vars = [
[NU_PLUGIN_IMAGE_FG, ($colors | get 07)]
[NU_PLUGIN_IMAGE_BG , ($colors | get 00)]
[NU_PLUGIN_IMAGE_BLACK , ($colors | get 00)]
[NU_PLUGIN_IMAGE_RED, ($colors | get 01)]
[NU_PLUGIN_IMAGE_GREEN, ($colors | get 02)]
[NU_PLUGIN_IMAGE_YELLOW, ($colors | get 03)]
[NU_PLUGIN_IMAGE_BLUE, ($colors | get 04)]
[NU_PLUGIN_IMAGE_MAGENTA, ($colors | get 05)]
[NU_PLUGIN_IMAGE_CYAN, ($colors | get 06)]
[NU_PLUGIN_IMAGE_WHITE, ($colors | get 07)]
[NU_PLUGIN_IMAGE_BRIGHT_BLACK, ($colors | get 08)]
[NU_PLUGIN_IMAGE_BRIGHT_RED, ($colors | get 09)]
[NU_PLUGIN_IMAGE_BRIGHT_GREEN, ($colors | get 10)]
[NU_PLUGIN_IMAGE_BRIGHT_YELLOW, ($colors | get 11)]
[NU_PLUGIN_IMAGE_BRIGHT_BLUE, ($colors | get 12)]
[NU_PLUGIN_IMAGE_BRIGHT_MAGENTA, ($colors | get 13)]
[NU_PLUGIN_IMAGE_BRIGHT_CYAN, ($colors | get 14)]
[NU_PLUGIN_IMAGE_BRIGHT_WHITE, ($colors | get 15)]
] | each {|col|
let rgb = $col | get 1
# 16bit rgb to 8bit = 0xe7e7 | bits and 0x00ff
let red = ($"0x($rgb | get 0)" | into int | bits and 0x00ff)
let green = ($"0x($rgb | get 1)" | into int | bits and 0x00ff)
let blue = ($"0x($rgb | get 2)" | into int | bits and 0x00ff)
let red_hx = ($red | fmt).lowerhex | str substring 2..
let green_hx = ($green | fmt).lowerhex | str substring 2..
let blue_hx = ($blue | fmt).lowerhex | str substring 2..
$"$env.($col | first) = 0x($red_hx)($green_hx)($blue_hx)"
}
if (confirm "write config to the env file?") {
let default = ($nu.env-path | path dirname | path join nu_image_plugin_conf.nu)
let config_path = input $"where should i save the env file? \(default: ($default)\)\n~> "
| if (not ($in | is-empty)) {
$in
} else {
($default)
}
if (not ( $config_path | path exists)) {
$"source ($config_path)" | save $nu.env-path --append
}
$"# Auto generated code\n($env_vars | str join "\n")" | save $config_path -f
log info "Please restart the shell"
} else {
for i in $env_vars {
print $"($i)\n"
}
print "add thse values to environment variables using `config env`"
}
}
if (confirm "do you want to save your current shell's theme as default for `to png`?") {
print (get-terminal-colors)
}

View File

@ -0,0 +1,71 @@
use image::RgbaImage;
use std::{
io::{BufReader, Read},
path::Path,
};
use vte::Parser;
use crate::{
ansi_to_image::{
font_family::FontFamily,
palette::Palette,
printer::{self, Settings},
},
warn,
};
use super::internal_scale::InternalScale;
pub fn make_image(
output_path: &Path,
font_family: FontFamily,
png_width: Option<u32>,
input: &[u8],
palette: Palette,
) {
// let = FontFamily::default();
let font = font_family.regular;
let font_bold = font_family.bold;
let font_italic = font_family.italic;
let font_italic_bold = font_family.bold_italic;
let font_height = 50.0;
let scale = InternalScale {
x: font_height,
y: font_height,
};
// let palette = Palette::Vscode;
let mut state_machine = Parser::new();
let mut performer = printer::new(Settings {
font,
font_bold,
font_italic,
font_italic_bold,
font_height,
scale,
palette,
png_width,
});
let reader = &mut BufReader::new(input);
let mut buf = [0; 2048];
loop {
match reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => state_machine.advance(&mut performer, &buf[..n]),
Err(err) => {
warn!("{err}");
break;
}
}
}
let image: RgbaImage = performer.into();
image.save(output_path).unwrap();
}

View File

@ -0,0 +1,21 @@
#[derive(Debug, Clone, Copy)]
pub(super) enum ColorType {
PrimaryForeground,
PrimaryBackground,
Normal(Color),
Bright(Color),
Fixed(u8),
Rgb { field1: (u8, u8, u8) },
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}

Some files were not shown because too many files have changed in this diff Show More