repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
nushell/modules/printer.nu
Rawdef run-command [command: string, args: list<string>] {
let result = (run-external $command ...$args | complete)
{
ok: ($result.exit_code == 0)
output: ($result.stdout | str trim)
error: ($result.stderr | str trim)
}
}
def first-line [text: string] {
let line = ($text | lines | first)
if $line == null { "" } else { $line }
}
def style [text: string, color: string] {
$"(ansi $color)($text)(ansi reset)"
}
def screen [] {
print -n $"(ansi cls)(ansi home)"
}
def key [] {
let event = (input listen --types [key])
$event.code
}
def default-printer [] {
let result = (run-command "lpstat" ["-d"])
if not $result.ok { return "" }
try {
$result.output
| parse --regex '^system default destination: (?<name>\S+)$'
| get 0.name
} catch { "" }
}
def printer-names [] {
let result = (run-command "lpstat" ["-p"])
if not $result.ok {
error make {msg: ($result.error | default "lpstat failed")}
}
$result.output
| lines
| where {|line| $line | str starts-with "printer "}
| each {|line|
try {
$line
| parse --regex '^printer (?<name>\S+) (?<summary>.*)$'
| get 0.name
} catch { null }
}
| where {|name| $name != null}
}
def printer-summary [name: string] {
let result = (run-command "lpstat" ["-p" $name])
let line = (first-line $result.output)
try {
$line
| parse --regex '^printer \S+ (?<summary>.*)$'
| get 0.summary
} catch {
if $result.error == "" { $line } else { $result.error }
}
}
def printer-acceptance [name: string] {
let result = (run-command "lpstat" ["-a" $name])
let line = (first-line $result.output)
if $line == "" { $result.error } else { $line }
}
def printer-device [name: string] {
let result = (run-command "lpstat" ["-v" $name])
try {
$result.output
| parse --regex '^device for \S+: (?<device>.*)$'
| get 0.device
} catch { "" }
}
def printer-alerts [name: string] {
let result = (run-command "lpstat" ["-l" "-p" $name])
$result.output
| lines
| each {|line| $line | str trim}
| where {|line| $line | str starts-with "Alerts:"}
| each {|line| $line | str replace "Alerts: " ""}
| str join ", "
}
def printer-jobs [name: string] {
let result = (run-command "lpstat" ["-o" $name])
if $result.output == "" { [] } else { $result.output | lines }
}
def ipp-value [xml: string, field: string] {
let lines = ($xml | lines)
let marker = $"<key>($field)</key>"
let matches = ($lines | enumerate | where item == $marker)
if ($matches | is-empty) { return "" }
let raw = ($lines | get (($matches | get 0 | get index) + 1) | str trim)
if ($raw | str contains "<true") { "true" } else if ($raw | str contains "<false") { "false" } else {
$raw | str replace --regex '^<[^>]+>(.*)</[^>]+>$' '$1'
}
}
def printer-ipp [name: string] {
let result = (run-command "ipptool" ["-q" "-X" "-T" "3" $"ipp://localhost/printers/($name)" "get-printer-attributes.test"])
if not $result.ok { return {} }
{
state: (ipp-value $result.output "printer-state")
accepting: (ipp-value $result.output "printer-is-accepting-jobs")
reasons: (ipp-value $result.output "printer-state-reasons")
message: (ipp-value $result.output "printer-state-message")
jobs: (ipp-value $result.output "queued-job-count")
}
}
def printer-logs [name: string] {
let files = (try { glob "/var/log/cups/error_log*" } catch { [] })
$files
| each {|file|
try {
open --raw $file
| lines
| where {|line| $line | str contains $name}
} catch { [] }
}
| flatten
| last 12
}
def printer-network [device: string] {
if not (($device | str starts-with "ipp://") or ($device | str starts-with "ipps://")) {
return "n/a"
}
let result = (run-command "ipptool" ["-q" "-t" "-T" "3" $device "get-printer-attributes.test"])
if $result.ok { "reachable" } else { "unreachable" }
}
def printer-record [name: string] {
let device = (printer-device $name)
let ipp = (printer-ipp $name)
{
name: $name
summary: (printer-summary $name)
acceptance: (printer-acceptance $name)
device: $device
alerts: (printer-alerts $name)
jobs: (printer-jobs $name)
ipp: $ipp
network: (printer-network $device)
logs: (printer-logs $name)
}
}
def state-label [summary: string] {
let lower = ($summary | str lowercase)
if ($lower | str contains "disabled") or ($lower | str contains "stopped") or ($lower | str contains "error") {
style "● DISABLED" "red_bold"
} else if ($lower | str contains "printing") {
style "● PRINTING" "yellow_bold"
} else {
style "● READY" "green_bold"
}
}
def render-list [names: list<string>, selected: int, notice: string] {
screen
print (style " PRINTERS " "cyan_bold")
print ""
let default = (default-printer)
for $item in ($names | enumerate) {
let marker = if $item.index == $selected { style ">" "cyan_bold" } else { " " }
let suffix = if $item.item == $default { style " default" "d" } else { "" }
print $" ($marker) ($item.item)($suffix)"
}
if $notice != "" {
print ""
print (style $notice "yellow")
}
print ""
print (style " ↑/↓ select enter open q quit " "d")
}
def render-detail [record: record, notice: string] {
screen
print (style $" ($record.name) " "cyan_bold")
print ""
print $" (state-label $record.summary) ($record.summary)"
print $" acceptance ($record.acceptance)"
print $" device ($record.device)"
print $" network ($record.network)"
let ipp_reasons = ($record.ipp.reasons? | default "")
let message = ($record.ipp.message? | default "")
if $ipp_reasons != "" { print $" reasons ($ipp_reasons)" }
if $message != "" { print $" message ($message)" }
if $record.alerts != "" { print $" alerts ($record.alerts)" }
print ""
let jobs = ($record.jobs | length)
let ipp_jobs = ($record.ipp.jobs? | default "")
if $ipp_jobs != "" { print $" jobs ($jobs) queued / ($ipp_jobs) reported" } else { print $" jobs ($jobs) queued" }
if $notice != "" {
print ""
print (style $notice "yellow")
}
print ""
print (style " p print j jobs l logs e enable+accept d default " "d")
print (style " r refresh ? help q quit " "d")
}
def render-jobs [record: record, selected: int, notice: string] {
screen
print (style $" ($record.name) / JOBS " "cyan_bold")
print ""
if ($record.jobs | is-empty) {
print " no queued jobs"
} else {
for $item in ($record.jobs | enumerate) {
let marker = if $item.index == $selected { style ">" "cyan_bold" } else { " " }
print $" ($marker) ($item.item)"
}
}
if $notice != "" {
print ""
print (style $notice "yellow")
}
print ""
print (style " ↑/↓ select c cancel h hold u release b back " "d")
}
def render-logs [record: record] {
screen
print (style $" ($record.name) / CUPS ERRORS " "cyan_bold")
print ""
if ($record.logs | is-empty) {
print " no matching entries in /var/log/cups/error_log*"
} else {
for $line in $record.logs { print $" ($line)" }
}
print ""
print (style " b back r refresh q quit " "d")
}
def render-help [name: string] {
screen
print (style $" ($name) / HELP " "cyan_bold")
print ""
print " p print a file"
print " j inspect and manage queued jobs"
print " l show matching CUPS error log entries"
print " e enable printer and accept new jobs"
print " d set user default printer"
print " r refresh status"
print " q quit"
print ""
print (style " press any key " "d")
}
def confirm [prompt: string] {
let answer = (input $"($prompt) [y/N] " | str lowercase | str trim)
$answer == "y" or $answer == "yes"
}
def action-enable [name: string] {
let enabled = (run-command "cupsenable" [$name])
if not $enabled.ok { return ($enabled.error | default "could not enable printer") }
let accepted = (run-command "cupsaccept" [$name])
if not $accepted.ok { return ($accepted.error | default "enabled, but could not accept jobs") }
"printer enabled; accepting jobs"
}
def action-default [name: string] {
let result = (run-command "lpoptions" ["-d" $name])
if $result.ok { "user default updated" } else { $result.error | default "could not set default printer" }
}
def action-print [name: string] {
let input_path = (input "file to print: " | str trim)
if $input_path == "" { return "cancelled" }
let file = ($input_path | path expand)
if not ($file | path exists) { return $"file not found: ($file)" }
let result = (run-command "lp" ["-d" $name $file])
if $result.ok { $result.output | default "print job submitted" } else { $result.error | default "print failed" }
}
def job-id [line: string] {
$line | split row -r '\s+' | first
}
def action-job [job: string, action: string] {
let id = (job-id $job)
let result = if $action == "cancel" {
run-command "cancel" [$id]
} else if $action == "hold" {
run-command "lp" ["-i" $id "-H" "hold"]
} else {
run-command "lp" ["-i" $id "-H" "resume"]
}
if $result.ok { $"job ($id): ($action)" } else { $result.error | default $"job action failed: ($action)" }
}
def printer-app [names: list<string>, selected: int, initial_view: string] {
mut view = $initial_view
mut selected_printer = $selected
mut selected_job = 0
mut notice = ""
loop {
if $view == "list" {
render-list $names $selected_printer $notice
let pressed = (key)
match $pressed {
"up" => { if $selected_printer > 0 { $selected_printer -= 1 } }
"down" => { if $selected_printer < (($names | length) - 1) { $selected_printer += 1 } }
"enter" => { $view = "detail"; $notice = "" }
"q" | "esc" => { break }
_ => {}
}
continue
}
let name = ($names | get $selected_printer)
let record = (printer-record $name)
if $view == "detail" {
render-detail $record $notice
let pressed = (key)
match $pressed {
"p" => { $notice = (action-print $name) }
"j" => { $selected_job = 0; $view = "jobs"; $notice = ""}
"l" => { $view = "logs"; $notice = "" }
"e" => { $notice = (action-enable $name) }
"d" => { $notice = (action-default $name) }
"r" => { $notice = "refreshed" }
"?" => { $view = "help"; $notice = "" }
"q" | "esc" => { break }
_ => {}
}
} else if $view == "jobs" {
render-jobs $record $selected_job $notice
let pressed = (key)
match $pressed {
"up" => { if $selected_job > 0 { $selected_job -= 1 } }
"down" => {
if not ($record.jobs | is-empty) and $selected_job < (($record.jobs | length) - 1) { $selected_job += 1 }
}
"c" => {
if not ($record.jobs | is-empty) and (confirm "cancel selected job?") { $notice = (action-job ($record.jobs | get $selected_job) "cancel") }
}
"h" => {
if not ($record.jobs | is-empty) and (confirm "hold selected job?") { $notice = (action-job ($record.jobs | get $selected_job) "hold") }
}
"u" => {
if not ($record.jobs | is-empty) { $notice = (action-job ($record.jobs | get $selected_job) "release") }
}
"b" | "esc" => { $view = "detail"; $notice = "" }
"q" => { break }
_ => {}
}
} else if $view == "logs" {
render-logs $record
let pressed = (key)
match $pressed {
"b" | "esc" => { $view = "detail" }
"r" => { $notice = "" }
"q" => { break }
_ => {}
}
} else {
render-help $name
let _ = (key)
$view = "detail"
}
}
}
export def main [name?: string] {
if (which lpstat | is-empty) { error make {msg: "lpstat is not installed"} }
if (which lp | is-empty) { error make {msg: "lp is not installed"} }
let names = (printer-names)
if ($names | is-empty) {
error make {msg: "CUPS has no configured printers"}
}
if $name != null and not ($name in $names) {
error make {msg: $"unknown printer: ($name)"}
}
let default = (default-printer)
let selected = if $name != null {
$names | enumerate | where item == $name | get 0.index
} else if $default != "" and ($default in $names) {
$names | enumerate | where item == $default | get 0.index
} else { 0 }
let initial_view = if $name != null or (($names | length) == 1) { "detail" } else { "list" }
printer-app $names $selected $initial_view
}