initial commit

This commit is contained in:
Benjamin VAUDOUR 2021-12-26 15:40:57 +00:00
parent 9ffc0e170f
commit 69b66ff555
22 changed files with 1774 additions and 0 deletions

View File

@ -1,2 +1,65 @@
# elv-lib
elv-lib includes some useful module functions and completions
## mods
This folder groups modules for different usages:
### file
file provides functions to complete a filename/directory motive with different methods:
* exact motive
* contains motive (with or without deep search)
- search by prefix (with or without deep search)
- search by suffix (with or without deep search)
- filter results by given extension
### format
format provides functions to beautify a given input. Examples:
- format:size returns a size (in bytes) in a human-readable format
- format:list displays data in columns
### ip
ip provides useful functions to manipulate IPv4 or IPv6 addresses.
### list
list provides functions to manipulate data of type list. Examples:
* list:foreach acts as builtin each with the callback function on the form {|i v| } where i is the index in the list an v the value
* list:reach acts as builtin each in reverse order
* list:filter filters in a list with the given callback criterium
* list:contains check if a list contains a specific entry or an entry which passes a callback
* etc.
### map
map provides functions to manipulate data of type map.
### num
num provides useful functions for number operations which are not part of builtin functions or math: module functions.
### option
### pdf
pdf provides functions to manipulate PDF files (qpdf is required to make this module work).
## completion
The folder completion contains the completion for some programs:
- [arc](https://github.com/mholt/archiver)
- [kcp](https://github.com/bvaudour/kcp)
- mpv
- pacman
- ssh

24
alias.elv Normal file
View File

@ -0,0 +1,24 @@
var dir = $E:HOME/.config/elvish/aliases
for file [(set _ = ?(put $dir/*.elv))] {
var content = (cat $file | slurp)
eval $content
}
{
use ./mods/common
edit:add-var cond~ $common:cond~
edit:add-var cexec~ $common:cexec~
}
{
use ./mods/num
edit:add-var '++~' $'num:++~'
edit:add-var '--~' $'num:--~'
edit:add-var neg~ $num:neg~
edit:add-var is-neg~ $num:is-neg~
edit:add-var is-zero~ $num:is-zero~
edit:add-var is-one~ $num:is-one~
edit:add-var min~ $num:min~
edit:add-var max~ $num:max~
}

9
completion.elv Normal file
View File

@ -0,0 +1,9 @@
use ./completion/arc
use ./completion/archiver
use ./completion/desarchiver
use ./completion/kcp
use ./completion/mpv
use ./completion/pacman
use ./completion/ssh
use ./completion/sudo
use ./completion/use

53
completion/arc.elv Normal file
View File

@ -0,0 +1,53 @@
use ./file
var commands = [
help
archive
unarchive
extract
ls
]
var description = [
&help='Display the help'
&archive='Create un new archive'
&unarchive='Extract all'
&extract='Extract a single file'
&ls='List the content'
]
var extensions = [ tar bz2 zip gz lz4 sz xz zst rar ]
fn -comp-commands {
each {|c|
edit:complex-candidate $c &display=(printf '%s (%s)' $c $description[$c])
} $commands
}
fn -comp-inline-files {|archive|
try {
arc ls $archive | eawk {|_ @argv| put $argv[-1] }
} except e {
nop
}
}
fn complete {|@argv|
var c cmd = (count $argv) $argv[1]
if (== $c 2) {
-comp-commands
} elif (== $c 3) {
if (not (has-value [help archive] $cmd)) {
file:complete $argv[-1] $@extensions
}
} else {
if (eq $cmd archive) {
edit:complete-filename $@argv
} elif (eq $cmd extract) {
var archive = $argv[2]
-comp-inline-files $archive
}
}
}
set edit:completion:arg-completer[arc] = $complete~

29
completion/archiver.elv Normal file
View File

@ -0,0 +1,29 @@
var options = [
z
l
x
b
g
h
]
var description = [
&z='zst compression (default)'
&l='lz4 compression'
&x='xz compression'
&b='bz2 compression'
&g='gzip compression'
&h='display help'
]
fn -options {
each {|o|
put [&short=$o &desc=$description[$o]]
} $options
}
fn complete {|@argv|
edit:complete-getopt $argv [(-options)] [ $edit:complete-filename~ ...]
}
set edit:completion:arg-completer[archiver] = $complete~

View File

@ -0,0 +1,10 @@
use ./file
var extensions = [ tar bz2 zip gz lz4 sz xz zst rar ]
fn complete {|@argv|
var m = $argv[-1]
file:complete $m $@extensions
}
set edit:completion:arg-completer[desarchiver] = $complete~

7
completion/file.elv Normal file
View File

@ -0,0 +1,7 @@
use ../mods/common
use ../mods/file
fn complete {|motive @extensions|
var type = (common:cond (eq $motive '') prefix deep-prefix)
file:match-extensions &type=$type $motive $@extensions
}

41
completion/kcp.elv Normal file
View File

@ -0,0 +1,41 @@
fn -remotes-packages { kcp -lN }
var options = [
-h
-v
-i
-di
-u
-l
-lN
-lS
-lI
-lO
-lx
-lxS
-lxI
-lxO
-lf
-s
-g
-V
]
var np = [
-i
-di
-s
-g
-V
]
fn complete {|@argv|
var c = (count $argv)
if (== $c 2) {
all $options
} elif (and (== $c 3) (has-value $np $argv[-2])) {
-remotes-packages
}
}
set edit:completion:arg-completer[kcp] = $complete~

53
completion/mpv.elv Normal file
View File

@ -0,0 +1,53 @@
use ./file
var extensions = [
aac
ape
avi
divx
flac
flv
m3u
m4a
m4v
mp3
mp4
mpeg
mpg
mkv
mng
mov
qt
oga
ogg
ogm
ogv
opus
ra
rv
ts
vob
wav
webm
wmv
wma
wmx
]
fn complete {|@argv|
var c = (count $argv)
if (== $c 2) {
put --speed
-files $argv[-1]
} elif (== $c 3) {
if (eq $argv[-2] --speed) {
put 0.8 0.9 1.0 1.1 1.2
} else {
file:complete $argv[-1] $@extensions
}
} else {
-files $argv[-1]
}
}
set edit:completion:arg-completer[mpv] = $complete~

99
completion/pacman.elv Normal file
View File

@ -0,0 +1,99 @@
use str
use ../mods/file
use ../mods/list
fn -local-packages { pacman -Q | eawk {|_ p @_| put $p } }
fn -repo-packages {
var packages = [(pacman -Ss | list:pforeach &step=2 {|_ v|
put $v
} | eawk {|_ p @_|
put $p
})]
var spackages = [&]
peach {|p|
str:split '/' $p
} $packages | peach {|e|
set spackages[$e] = $nil
}
keys $spackages
all $packages
}
var options = [
-h
-V
-Q
-Qs
-Ql
-Qi
-Qm
-Qdt
-Qo
-R
-Rsn
-S
-Ss
-Si
-Sii
-Syu
-Syyu
-U
-D
]
var asdeps = [
-S
-U
-D
]
var lpack = [
-Q
-Qs
-Ql
-Qi
-D
-R
-Rsn
]
var rpack = [
-S
-Ss
-Si
-Sii
]
var dpack = [
-U
]
var fpack = [
-Qo
]
var extensions = [ tar.zst tar.xz tar.gz tar.bz2 ]
fn complete {|@argv|
var c = (count $argv)
if (< $c 3) {
all $options
} else {
var cmd = $argv[1]
if (and (== $c 3) (has-value $asdeps $cmd)) {
put --asdeps --asexplicit
}
if (has-value $lpack $cmd) {
-local-packages
} elif (has-value $rpack $cmd) {
-repo-packages
} elif (has-value $dpack $cmd) {
file:complete $argv[-1] $@extensions
} elif (has-value $fpack $cmd) {
edit:complete-filename $argv[-1]
}
}
}
set edit:completion:arg-completer[pacman] = $complete~

95
completion/ssh.elv Normal file
View File

@ -0,0 +1,95 @@
use path
use re
use str
use ../mods/common
use ../mods/list
use ../mods/map
use ../mods/option
var options-ssh = [ 1 2 4 6 A D f g I i L l m o v a b C c e F k N n p q R s T t X x ]
var options-scp = [ 3 4 6 B C p q r v c F i l o P S ]
fn -kh {
cat ~/.ssh/known_hosts | peach {|l|
put [(str:split ' ' $l)]
} | peach {|e|
var domains @_ = $@e
str:split ',' $domains
}
}
fn -port {|cmd @argv|
var o = (common:cond (eq $cmd 'ssh') '-p' '-P')
var margs = (option:map $argv)
var p = (map:value-of $margs $o &default=[])
cond:cexec (list:is-empty $p) 22 { put $p[-1] }
}
fn -complete-names {
var fp = $E:HOME/.config/elvish/private/sshnames
if (path:is-regular $fp) {
cat $fp | from-lines | each {|n| put (printf '%s@' $n) }
}
}
fn -complete-domains {|name hosts|
each {|h|
put (printf '%s@%s' $name $h)
} $hosts
}
fn -complete-remote-dir {|port address dir|
var cmd = (printf ^
"for f in '%s'*; do if [[ -d $f ]]; then echo $f/; else echo $f; fi; done" ^
$dir)
try {
ssh -p $port $address $cmd | each {|f|
put (printf '%s:%s' $address $f)
}
} except e { }
}
fn -complete-args {|hosts cmd @argv|
var arg = $argv[-1]
var i = (str:index $arg @)
if (< $i 0) {
-complete-names
all $hosts
if (eq $cmd scp) {
edit:complete-filename $cmd $@argv
}
return
}
var n h = $arg[..$i] $arg[(+ $i 1)..]
if (eq $cmd scp) {
set i = (str:index $h :)
if (>= $i 0) {
var d = $h[(+ $i 1)..]
set h = $h[..$i]
if (list:contains $h $hosts) {
var p = (-port $cmd @argv)
-complete-remote-dir $p $n@$h $d
}
return
}
}
-complete-domains $n $hosts
}
fn complete {|@argv|
var @hosts = (-kh)
var cmd = $argv[0]
var is-ssh = (eq $cmd ssh)
var po = (common:cond $is-ssh -p -P)
if (<= (count $argv) 2) {
all (common:cond $is-ssh $options-ssh $options-scp)
-complete-args $hosts $@argv
} elif (eq $argv[-2] $po) {
put 22
} else {
-complete-args $hosts $@argv
}
}
set edit:completion:arg-completer[scp] = $complete~
set edit:completion:arg-completer[ssh] = $complete~

10
completion/sudo.elv Normal file
View File

@ -0,0 +1,10 @@
fn complete {|@argv|
if (and (> (count $argv) 2) (has-key $edit:completion:arg-completer $argv[1])) {
$edit:completion:arg-completer[$argv[1]] (all $argv[1:])
} else {
edit:complete-sudo $@argv
}
}
set edit:completion:arg-completer[sudo] = $edit:complete-sudo~
#edit:completion:arg-completer[sudo] = $-complete~

31
completion/use.elv Normal file
View File

@ -0,0 +1,31 @@
use path
use str
use ./file
var libdir = $E:HOME/.config/elvish/lib
var builtin_modules = [
builtin
epm
file
math
path
re
readline-binding
store
str
unix
]
set edit:completion:arg-completer[use] = {|@argv|
use str
use path
all $builtin_modules
put $libdir/**.elv | each {|f|
if (path:is-regular $f) {
str:trim-prefix $f $libdir/
}
} | each {|f| str:trim-suffix $f .elv }
if (> (count $argv) 1) {
file:complete $argv[-1] elv | each {|f| str:trim-suffix $f .elv }
}
}

27
mods/common.elv Normal file
View File

@ -0,0 +1,27 @@
fn to-list {|@argv|
var c = (count $argv)
if (== $c 0) {
put [ (all) ]
} elif (== $c 1) {
put $argv[0]
} else {
fail (printf '0 or 1 argument needed given %d arguments: %v' $c $argv)
}
}
fn cond {|cond v1 v2|
if $cond {
put $v1
} else {
put $v2
}
}
fn cexec {|cond v1 v2|
var r = (cond $cond $v1 $v2)
if (eq (kind-of $r) fn) {
$r
} else {
put $r
}
}

61
mods/file.elv Normal file
View File

@ -0,0 +1,61 @@
use str
use ./list
use ./map
var search-type = [
&exact= {|m| ls $m 2>/dev/null }
&match= {|m| put *$m* }
&prefix= {|m| put $m* }
&suffix= {|m| put *$m }
&deep-match= {|m| put **$m** }
&deep-prefix= {|m| put $m** }
&deep-suffix= {|m| put **$m }
]
fn comp {|f1 f2|
var fl1 fl2 = (str:to-lower $f1) (str:to-lower $f2)
var c = (str:compare $fl1 $fl2)
if (!= $c 0) {
put $c
} else {
str:compare $f1 $f2
}
}
fn -r {|sort result|
if $sort {
keys $result | list:sort $comp~
} else {
keys $result
}
}
fn -s {|&sort=$false &type=exact @motive|
var f result = $search-type[$type] [&]
if (list:is-empty $motive) {
set result = (put * | map:to-set)
} else {
peach {|m|
try {
$f $m | peach {|e|
set result[$e] = $nil
}
} except e { }
} $motive
}
-r $sort $result
}
fn match {|&sort=$false &type=prefix @motive|
-s &sort=$sort &type=$type $@motive
}
fn match-extensions {|&sort=$false &type=deep-prefix motive @extensions|
var result = [&]
-s &type=$type $motive | peach {|f|
if (list:contains {|e| str:has-suffix $f .$e } $extensions) {
set result[$f] = $nil
}
}
-r $sort $result
}

198
mods/format.elv Normal file
View File

@ -0,0 +1,198 @@
use math
use re
use str
use ./common
fn chars {|v|
str:split '' $v
}
fn len {|v|
chars $v | count
}
fn string {|v|
var k = (kind-of $v)
if (eq $k string) {
put $v
} elif (eq $k number) {
to-string $v
} elif (eq $k bool) {
common:cond $v 'X' ''
} elif (eq $k list) {
each $string~ $v | str:join ''
} elif (eq $v $nil) {
put ''
} else {
to-string $v
}
}
fn int {|v|
var k = (kind-of $v)
if (eq $k 'string') {
try {
var n = (math:trunc $v)
var s @_ = (str:split '.' (to-string $n))
put $s
} except e {
fail (printf '%s nest pas un nombre' $v)
}
} elif (eq $k 'number') {
int (to-string $v)
} elif (eq $k 'bool') {
common:cond $v 1 0
} else {
put 0
}
}
fn repeat {|n s|
use builtin
builtin:repeat $n $s | str:join ''
}
fn blank {|n|
repeat $n ' '
}
fn left {|str size|
var l = (len $str)
if (< $l $size) {
string [ $str (blank (- $size $l)) ]
} elif (== $l $size) {
put $str
} else {
string [ (chars $str) ][..$size]
}
}
fn right {|str size|
var l = (len $str)
if (< $l $size) {
string [ (blank (- $size $l)) $str ]
} elif (== $l $size) {
put $str
} else {
string [ (chars $str) ][(- $l $size)..]
}
}
fn center {|str size|
var l = (len $str)
if (< $l $size) {
var d = (- $size $l)
var b = (math:trunc (/ $d 2))
var e = (- $d $b)
string [ (blank $b) $str (blank $e) ]
} elif (== $l $size) {
put $str
} else {
var d = (- $l $size)
var b = (math:trunc (/ $d 2))
var e = (+ $b $size)
string [ (chars $str) ][$b..$e]
}
}
var -align = [
&center=$center~
&right=$right~
&left=$left~
]
fn column {|&align=left name @label|
var c = [
&name=$name
&align=$-align[$align]
&size=0
&label=''
]
if (> (count $label) 0) {
set c[label] = $label[0]
set c[size] = (count $label[0])
}
put $c
}
fn update-props {|props data|
each {|d|
set @props = (each {|c|
var n = $c[name]
if (has-key $d $n) {
var v = (string $d[$n])
var l = (count $v)
if (< $c[size] $l) {
set c[size] = $l
}
}
put $c
} $props)
} $data
put $props
}
fn line-header {|&sep=' ' props|
var @data = (each {|c|
$c[align] $c[label] $c[size]
} $props)
str:join $sep $data
}
fn line {|&sep=' ' props line|
var @data = (each {|c|
var n v = $c[name] ''
if (has-key $line $n) {
set v = (string $line[$n])
}
$c[align] $v $c[size]
} $props)
str:join $sep $data
}
fn list {|&with-header=$true &csep=' ' &hsep='-' &recompute=$true props data|
if $recompute {
set props = (update-props $props $data)
}
if $with-header {
echo (line-header &sep=$csep $props)
if (bool $hsep) {
var s = (* (- (count $props) 1) (count $csep))
each {|c|
set s = (+ $s $c[size])
} $props
echo (repeat (math:trunc (/ $s (count $hsep))) $hsep)
}
}
each {|d|
echo (line &sep=$csep $props $d)
} $data
}
fn size {|size|
var u = 0
var m = [
&10=Kio
&20=Mio
&30=Gio
&40=Tio
&50=Pio
&60=Eio
&70=Zio
&80=Yio
]
while (< $u 80) {
var p = (math:pow 2 (+ $u 10))
if (< $size $p) {
break
}
set u = (to-string (+ $u 10))
}
if (== $u 0) {
put $size
} else {
var p = (math:pow 2 $u)
var e = (/ $size $p)
printf '%.1f%s' $e $m[$u]
}
}

154
mods/ip.elv Normal file
View File

@ -0,0 +1,154 @@
use re
use str
use ./common
use ./list
use ./num
fn is-ipv4 {|arg|
common:cexec ^
(re:match '^([0-9]{1,3}\.){3}[0-9]{1,3}$' $arg) ^
{ not (str:split '.' $arg | list:contains {|p| > $p 255 }) } ^
$false
}
fn is-ipv6 {|arg|
var p = '[0-9a-fA-F]{1,4}'
var g1 g2 = (printf '(%s:)' $p) (printf '(:%s)' $p)
var cases = [
(printf '%s{7,7}%s' $g1 $p)
(printf '%s{1,7}:' $g1)
(printf '%s{1,6}:%s' $g1 $p)
(printf '%s{1,5}%s{1,2}' $g1 $g2)
(printf '%s{1,4}%s{1,3}' $g1 $g2)
(printf '%s{1,3}%s{1,4}' $g1 $g2)
(printf '%s{1,2}%s{1,5}' $g1 $g2)
(printf '%s:%s{1,6}' $p $g2)
(printf ':%s{1,7}' $g2)
'::'
]
var r = (printf '^(%s)$' (str:join '|' $cases))
re:match $r $arg
}
fn is-ip {|arg|
or (is-ipv4 $arg) (is-ipv6 $arg)
}
fn -l6 {|p|
set p = (str:to-lower $p)
var c = (- 4 (count $p))
put (repeat $c '0') $p | str:join ''
}
fn -m6 {|p|
while (and (> (count $p) 1) (eq $p[0] 0)) {
set p = $p[1..]
}
put $p
}
fn -max0 {|parts|
var idx s = -1 1
var ci cs f = -1 0 $false
list:foreach {|i p|
if (eq $p 0) {
if $f {
set cs = (num:++ $cs)
} else {
set ci cs f = $i 1 $true
}
} elif $f {
set f = $false
if (> $cs $s) {
set idx s = $ci $cs
}
}
} $parts
if (and $f (> $cs $s)) {
set idx s = $ci $cs
}
put $idx $s
}
fn long6 {|ip|
if (not (is-ipv6 $ip)) {
fail 'Not an IPv6'
}
if (eq $ip '::') {
repeat 8 '0000' | str:join ':'
return
}
var c = (- 7 (str:count $ip ':'))
if (> $c 0) {
var i = (str:index $ip '::')
var z = (repeat $c ':' | str:join '')
set ip = (str:join '' [$ip[..$i] $z $ip[$i..]])
}
str:split ':' $ip | each $-l6~ | str:join ':'
}
fn middle6 {|ip|
str:split ':' (long6 $ip) | each $-m6~ | str:join ':'
}
fn short6 {|ip|
var parts = [(str:split ':' (middle6 $ip))]
var i s = (-max0 $parts)
if (>= $i 0) {
var left right = $parts[..$i] $parts[(+ $i $s)..]
if (== (count $left) 0) {
set left = ['']
}
if (== (count $right) 0) {
set right = ['']
}
set @parts = $@left '' $@right
}
str:join ':' $parts
}
fn -cmp {|e1 e2|
var c = 0
list:foreach {|i p1|
var p2 = $e2[$i]
set c = (compare $p1 $p2)
if (!= $c 0) {
break
}
} $e1
put $c
}
fn comp4 {|ip1 ip2|
if (or (not (is-ipv4 $ip1)) (not (is-ipv4 $ip2))) {
fail (printf 'Not an IPv4: %s → %s' $ip1 $ip2)
}
-cmp [(str:split . $ip1)] [(str:split . $ip2)]
}
fn comp6 {|ip1 ip2|
if (or (not (is-ipv6 $ip1)) (not (is-ipv6 $ip2))) {
fail (printf 'Not an IPv6: %s → %s' $ip1 $ip2)
}
-cmp [(str:split : (middle6 $ip1))] [(str:split : (middle6 $ip2))]
}
fn comp {|ip1 ip2|
if (is-ipv4 $ip1) {
if (is-ipv4 $ip2) {
comp4 $ip1 $ip2
} else {
put -1
}
} elif (is-ipv6 $ip1) {
if (is-ipv4 $ip2) {
put 1
} elif (is-ipv6 $ip2) {
comp6 $ip1 $ip2
} else {
put -1
}
} else {
fail (printf 'Not an IP: %s → %s' $ip1 $ip2)
}
}

178
mods/list.elv Normal file
View File

@ -0,0 +1,178 @@
use ./common
use ./num
var -l~ = $common:to-list~
var -c~ = $common:cexec~
var '++~' = $'num:++~'
var is-neg~ = $num:is-neg~
fn indexes {|&from=0 &step=1 @argv|
var c = (count (-l $@argv))
var from = (-c (is-neg $from) (+ $from $c) $from)
var to = (-c (is-neg $step) -1 $c)
if (and (>= $from 0) (< $from $c)) {
range &step=$step $from $to
}
}
fn loop {|&from=0 &step=1 @argv|
var l = (-l $@argv)
indexes &from=$from &step=$step $l | each {|i|
put [ $i $l[$i] ]
}
}
fn foreach {|&from=0 &step=1 &end=$false cb @argv|
if (not $end) {
loop &from=$from &step=$step $@argv | each {|e| $cb $@e}
} else {
loop &from=$from &step=$step $@argv | each {|e|
if ($end $@e) {
break
}
$cb $@e
}
}
}
fn pforeach {|&from=0 &step=1 cb @argv|
loop &from=$from &step=$step $@argv | peach {|e| $cb $@e}
}
fn reach {|cb @argv|
foreach &from=-1 &step=-1 {|_ e| $cb $e} $@argv
}
fn reverse {|@argv|
reach $put~ $@argv
}
fn is-empty {|@argv|
num:is-zero (count (-l $@argv))
}
fn is-not-empty {|@argv|
not (is-empty $@argv)
}
fn filter {|cb @argv|
each {|e|
-c ($cb $e) $e $nop~
} (-l $@argv)
}
fn filter-not {|cb @argv|
filter {|v| not ($cb $v)} $@argv
}
fn contains {|cb @argv|
var cb2~ = (common:cond (eq (kind-of $cb) fn) $cb {|e| eq $cb $e})
each {|e|
if (cb2 $e) {
put $true
return
}
} (-l $@argv)
put $false
}
fn contains-not {|cb @argv|
not (contains $cb $@argv)
}
fn search {|cb @argv|
foreach {|i e|
-c ($cb $i $e) [$i $e] $nop~
} $@argv
}
fn search-not {|cb @argv|
search {|i e| not ($cb $i $e) } $@argv
}
fn exists {|cb @argv|
var r = $false
foreach {|i e|
if ($cb $i $e) {
set r = $true
break
}
} $@argv
put $r
}
fn exists-not {|cb @argv|
not (exists $cb $@argv)
}
fn search-first {|&from=$nil &reverse=$false cb @argv|
var step = (-c $reverse -1 1)
if (not $from) {
set from = (-c $reverse -1 0)
}
foreach &from=$from &step=$step {|i e|
if ($cb $i $e) {
put [$i $e]
break
}
} $@argv
}
fn first {|&from=$nil &reverse=$false cb @argv|
search-first &from=$from &reverse=$reverse {|_ e| $cb $e} $@argv | each {|e|
put $e[1]
break
}
}
fn reduce {|acc cb @argv|
each {|e|
set acc = ($cb $acc $e)
} (-l $@argv)
put $acc
}
fn remove-duplicate {|@argv|
var done = [&]
each {|e|
if (not (has-key $done $e)) {
set done[$e] = $nil
put $e
}
} (-l $@argv)
}
fn swap {|i j @argv|
var l = (-l $@argv)
var c = (count $l)
if (< $i 0) {
set i = (+ $i $c)
}
if (< $j 0) {
set j = (+ $j $c)
}
if (or (is-neg $i) (is-neg $j) (>= $i $c) (>= $j $c)) {
fail 'index out of range'
}
if (== $i $j) {
all $l
} else {
if (> $i $j) {
set i j = $j $i
}
take $i $l
put $l[$j]
all $l[(++ $i)..$j]
put $l[$i]
drop (++ $j) $l
}
}
fn less {|cb|
put {|a b| < ($cb $a $b) 0 }
}
fn sort {|cb @argv|
order &less-than=(less $cb) (-l $@argv)
}

81
mods/map.elv Normal file
View File

@ -0,0 +1,81 @@
use ./common
use ./list
use ./num
fn is-empty {|m|
num:is-zero (count $m)
}
fn values {|m|
keys $m | each {|k| put $m[$k]}
}
fn pvalues {|m|
keys $m | peach {|k| put $m[$k]}
}
fn value-of {|m k &default=$nil|
common:cexec (has-key $m $k) { put $m[$k] } { put $default }
}
fn unzip {|m|
var keys values = [] []
keys $m | each {|k|
set @keys = $@keys $k
set @values = $@values $m[$k]
}
put $keys $values
}
fn zip {|keys values|
var ck cv = (count $keys) (count $values)
var c = (num:min [$ck $cv])
var m = [&]
range $c | peach {|i|
put [&k=$keys[$i] &v=$values[$i]]
} | each {|e|
set m[$e[k]] = $e[v]
}
put $m
}
fn to-map {|@argv|
var l = (list:-l $@argv)
zip [(list:indexes $l)] $l
}
fn to-set {|@argv|
var m = [&]
each {|k|
set m[$k] = $nil
} (list:-l $@argv)
put $m
}
fn mult-dissoc {|m @argv|
each {|k|
set m = (dissoc $m $k)
} $argv
put $m
}
fn mult-assoc {|&replace=$true m @argv|
each {|e|
var k v = $@e
if (or $replace (not (has-key $m $k))) {
set m = (assoc $m $k $v)
}
} $argv
put $m
}
fn add {|m k @values|
var values = (list:-l $values)
set m[$k] = (common:cexec (has-key $m $k) { put [(all $m[$k]) $@values ] } $values)
put $m
}
fn foreach {|cb @m|
set m = (common:cexec (list:is-empty $m) $one~ { put $m[0] })
keys $m | each {|k| put [$k $m[$k]]} | each {|e| $cb $@e}
}

46
mods/num.elv Normal file
View File

@ -0,0 +1,46 @@
fn ++ {|n|
+ $n 1
}
fn -- {|n|
- $n 1
}
fn neg {|n|
* $n -1
}
fn is-zero {|n|
== $n 0
}
fn is-neg {|n|
< $n 0
}
fn is-one {|n|
== $n 1
}
fn -minmax {|cb @numbers|
use ./common
var l = (common:to-list $@numbers)
if (is-zero (count $l)) {
return
}
var m = $l[0]
all $l[1..] | each {|n|
if ($cb $n $m) {
set m = $n
}
}
put $m
}
fn min {|@numbers|
-minmax $'<~' $@numbers
}
fn max {|@numbers|
-minmax $'>~' $@numbers
}

85
mods/option.elv Normal file
View File

@ -0,0 +1,85 @@
use re
use str
use ./common
use ./list
use ./map
use ./num
fn is-aggregate {|option|
re:match '^\-\w{2,}$' $option
}
fn is-short {|option|
re:match '^\-\w$' $option
}
fn is-long {|option|
re:match '^\-\-\w+(=.*)?$' $option
}
fn is-option {|option|
or (is-short $option) (is-long $option) (is-aggregate $option)
}
fn split {|option|
if (is-aggregate $option) {
each {|e| put -$e} $option[1..]
} elif (is-long $option) {
var i = (str:index $option '=')
if (num:is-neg $i) {
put $option
} else {
put $option[..$i] $option[(num:++ $i)..]
}
} else {
put $option
}
}
fn -j {|options|
var o = (each {|o| put $o[1..] } $options | str:join '')
if (not-eq $o '') {
put -$o
}
}
fn join {|@options|
set options = (common:to-list $@options)
var cb = {|_ o| or (is-short $o) (is-aggregate $o) }
var i0 = 0
list:search-not $cb $options | each {|e|
var i1 v = $@e
-j $options[$i0..$i1]
put $v
set i0 = (num:++ $i1)
}
-j $options[$i0..]
}
fn expand {|@argv|
each $split~ (common:to-list $@argv)
}
fn map {|@argv|
set @argv = (expand $@argv)
var result i c = [&] 0 (count $argv)
var last = (num:-- $c)
while (< $i $c) {
var e = $argv[$i]
if (is-option $e) {
var k v = $e []
if (< $i $last) {
var j = (num:++ $i)
set e = $argv[$j]
if (not (is-option $e)) {
set i @v = $j $@v $e
}
}
set result = (map:add $result $k $v)
} else {
set result = (map:add $result '' [$e])
}
set i = (num:++ $i)
}
put $result
}

420
mods/pdf.elv Normal file
View File

@ -0,0 +1,420 @@
use path
use re
use str
use ./common
use ./map
fn is-pdf {|file|
eq (path:ext $file) '.pdf'
}
fn exist {|file|
or (path:is-dir &follow-symlink=$true $file) (path:is-regular &follow-symlink=$true $file)
}
fn not-exist {|file|
not (exist $file)
}
fn is-pdf-exist {|file|
and (is-pdf $file) (exist $file)
}
fn is-number {|v|
re:match '^\d+$' (to-string $v)
}
fn is-page-selection {|v|
re:match '^(r?\d+|z)(-(r?\d+|z))?(,(r?\d+|z)(-(r?\d+|z))?)*(:(even|odd))?$' (to-string $v)
}
fn is-rotate-selection {|v|
re:match '^(\+|-)?\d+(:(r?\d+|z)(-(r?\d+|z))?(,(r?\d+|z)(-(r?\d+|z))?)*(:(even|odd))?)?$' (to-string $v)
}
fn is-selection {|v|
or (is-page-selection $v) (is-rotate-selection $v)
}
fn is-object {|v|
re:match '^ \d+\ 0\ R$' $v
}
fn version {|in|
var version = (head -n 1 $in)
set version @_ = (str:split "\r" $version)
str:trim-prefix $version '%PDF-'
}
fn json {|in|
qpdf $in --json | from-json
}
fn form {|in|
var json = (json $in)
put $json[acroform]
}
fn encryption {|in|
var json = (json $in)
put $json[encrypt]
}
fn objects {|in|
var json = (json $in)
put $json[objects]
}
fn -format {|dict o|
var t = (kind-of $o)
if (eq $t string) {
if (has-key $dict $o) {
set o = $dict[$o]
}
} elif (eq $t list) {
set @o = (all $o | each {|e|
common:cexec (has-key $dict $e) { put $dict[$e] } $e
})
} elif (eq $t map) {
map:foreach {|k v|
set o[$k] = (-format $dict $v)
} $o
}
put $o
}
fn -deep {|&dict=$nil objects @keys|
if (== (count $keys) 0) {
common:cexec (not-eq $dict $nil) { -format $dict $objects } $objects
put $true
return
}
if (not-eq $dict $nil) {
set objects = (-format $dict $objects)
}
var id @next = $@keys
var t = (kind-of $objects)
if (eq $t map) {
if (has-key $objects $id) {
-deep &dict=$dict $objects[$id] $@next
}
} elif (eq $t list) {
if (and (is-number $id) (< $id (count $objects))) {
-deep &dict=$dict $objects[$id] $@next
}
} else {
put $nil $false
}
}
fn -object {|&extend=$false objects id|
var dict = (common:cond $extend $objects $nil)
var o _ = (-deep &dict=$dict $objects $id)
put $o
}
fn deep {|&extend=$false in @keys|
var objects = (objects $in)
var dict = (common:cond $extend $objects $nil)
-deep &dict=$dict $dict $@keys
}
fn contains {|in @keys|
var _ ok = (deep $in $@keys)
put $ok
}
fn value {|&extend=$false in @keys|
var v _ = (deep &extend=$extend $in $@keys)
put $v
}
fn object {|&extend=$false in key|
-object &extend=$extend (objects $in) $key
}
fn -filter {|&extend=$false objects cond|
var out = [&]
map:foreach {|k o|
if ($cond $o) {
set out[$k] = $o
}
} $objects
put $out
}
fn filter {|&extend=$false in cond|
-filter &extend=$extend (objects $in) $cond
}
fn font-filter {|o|
and ^
(eq (kind-of $o) map) ^
(has-key $o /Type) ^
(eq $o[/Type] /Font)
}
fn fonts {|in|
var dict = (objects $in)
var fonts = (-filter $dict $font-filter~)
put $fonts | map:foreach {|id f|
map:foreach {|k v|
if (has-key $dict $v) {
set f[$k] = $dict[$v]
}
} $f
var fd = $f[/FontDescriptor]
map:foreach {|k v|
if (and (not-eq $k /FontFile2) (has-key $dict $v)) {
set fd[$k] = $dict[$v]
}
} $fd
set f[/FontDescriptor] = $fd
set f[id] = $id
put $f
}
}
fn image-filter {|o|
and ^
(eq (kind-of $o) map) ^
(has-key $o /Subtype) ^
(eq $o[/Subtype] /Image)
}
fn images {|in|
var json = (json $in)
var dict = $json[objects]
all $json[pages] | each {|p|
var n = $p[pageposfrom1]
all $p[images] | each {|i|
var id = $i[object]
var img = (-object $dict $id)
map:foreach {|k v|
if (has-key $dict $v) {
set img[$k] = $dict[$v]
}
} $img
if (has-key $img /ColorSpace) {
var @cs = (all $img[/ColorSpace] | each {|v|
if (has-key $dict $v) {
put $dict[$v]
} else {
put $v
}
})
set img[/ColorSpace] = $cs
}
if (has-key $img /DecodeParms) {
var dp = $img[/DecodeParms]
map:foreach {|k v|
if (has-key $dict $v) {
set dp[$k] = $dict[$v]
}
} $dp
set img[/DecodeParms] = $dp
}
set img[id] = $id
set img[page] = $n
set img[name] = $i[name]
put $img
}
}
}
fn trailer {|&extend=$false in|
object &extend=$extend $in 'trailer'
}
fn pages {|in|
var json = (json $in)
put $json[pages]
}
fn page {|in nb|
put (pages $in)[(- $nb 1)]
}
fn nb-pages {|in|
qpdf --show-npages $in
}
fn attachments {|in|
var data = []
var json = (json $in)
var attachments = $json[attachments]
if (> (count $attachments) 0) {
set @data = (qpdf --list-attachments $in | eawk {|_ f @_|
put $f
})
}
put $data
}
fn parse-selection {|@selection|
var out = []
var in = $nil
each {|e|
if (is-pdf $e) {
if (not-exist $e) {
fail (printf '%s: le fichier nexiste pas' $e)
}
if (not-eq $in $nil) {
set @out = $@out $in
}
set in = [
&file=$e
&rotate=$nil
&selections=[]
]
continue
}
if (eq $in $nil) {
fail (printf '%s: pas de fichier avant la sélection' $e)
}
var r rc = $in[rotate] $nil
if (is-page-selection $e) {
set rc = $false
} elif (is-rotate-selection $e) {
set rc = $true
} else {
fail (printf '%s: paramètre invalide' $e)
}
if (not-eq $r $rc) {
if (not-eq $r $nil) {
set @out = $@out $in
set in[selections] = []
}
set in[rotate] = $rc
}
set in[selections] = [ (all $in[selections]) $e ]
} $selection
if (not-eq $in $nil) {
set @out = $@out $in
}
put $out
}
fn -t {
mktemp -q /tmp/qpdf_XXXXXXXXXX.pdf
}
fn rotate {|&empty=$false &out=$nil &keep=$false in @rotate|
var @args = $in (common:cond (eq $out $nil) --replace-input $out)
if $empty {
set @args = $@args --empty
}
if $keep {
set @rotate = (each {|r| put --rotate=$r } $rotate)
qpdf $@args $@rotate
return
}
var tmp = []
try {
var @tmp = (each {|r|
var t r @s = (-t) (str:split ':' $r)
set s = (str:join ':' $s)
if (eq $s '') {
qpdf $in $t --rotate=$r
} else {
qpdf $in $t --pages $in $s --
qpdf $t --replace-input --rotate=$r
}
put $t
} $rotate)
qpdf $@args --pages $@tmp --
} finally {
rm -f $@tmp
}
}
fn -catarg {|selection|
var f r s = $selection[file] $selection[rotate] $selection[selections]
var out tmp = [] []
if $r {
var t = (-t)
set @tmp = $t
set @out = $@out $t
rotate &out=$t $f $@s
} else {
set @out = $@out $f
if (> (count $s) 0) {
set @out = $@out (str:join ',' $s)
}
}
put $out $tmp
}
fn cat {|&empty=$false &collate=$nil &out=$nil @selection|
var inputs = (parse-selection $@selection)
var pages tmp = [] []
each {|in|
var a t = (-catarg $in)
set @pages = $@pages $@a
set @tmp = $@tmp $@t
} $inputs
try {
var in = $pages[0]
var @args = $in (common:cond (eq $out $nil) --replace-input $out)
if $empty {
set @args = $@args --empty
}
if $collate {
var c = --collate(common:cexec (is-number $collate) { put =$collate } '')
set @args = $@args $c
}
qpdf $@args --pages $@pages --
} finally {
each {|t|
rm -f $t
} $tmp
}
}
fn decrypt {|&out=$nil in @passwd|
var @args = $in (common:cond (eq $out $nil) --replace-input $out) --decrypt
if (> (count $passwd) 0) {
set @args = $@args $passwd[0]
}
qpdf $@args
}
fn -l0 {|n size|
set n = (to-string $n)
var l = (- $size (count $n))
str:join '' [ (repeat $l 0) $n ]
}
fn split {|&empty=$false &size=1 in out|
if (not-exist $out) {
mkdir $out
}
var @args = (common:cexec $empty --empty $nop~)
qpdf $@args --split-pages=$size $in $out'/%d.pdf'
}
fn uncat {|&empty=$false in out @selection|
if (not-exist $out) {
mkdir $out
}
var i = 1
var n = (count $selection)
set n = (count (to-string $n))
each {|s|
cat &empty=$empty &out=$out'/'(-l0 $i $n)'.pdf' $in $s
set i = (+ $i 1)
} $selection
}
fn raw-stream {|in out id|
qpdf $in --show-object=$id --raw-stream-data > $out
}
fn filtered-stream {|in out id|
qpdf $in --show-object=$id --filtered-stream-data --normalize-content=n --stream-data=uncompress --decode-level=all > $out
}
fn attachment {|in out id|
qpdf $in --show-attachment=$id > $out
}