From 4a9e524f6654de2d63614537d4ce8bb0811b7885 Mon Sep 17 00:00:00 2001 From: rhiannon morris Date: Wed, 2 Apr 2025 18:44:25 +0200 Subject: [PATCH] add silly little scripts --- flake.nix | 4 +- scripts/niss-misc.nix | 53 +++++++ scripts/niss-misc/charFilters/little | 57 ++++++++ scripts/niss-misc/charFilters/wide | 19 +++ scripts/niss-misc/find-parent/find-parent | 32 +++++ scripts/niss-misc/galleryHelpers/make-info | 67 +++++++++ scripts/niss-misc/galleryHelpers/make-sizes | 147 ++++++++++++++++++++ scripts/niss-misc/nd/nd | 19 +++ 8 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 scripts/niss-misc.nix create mode 100644 scripts/niss-misc/charFilters/little create mode 100644 scripts/niss-misc/charFilters/wide create mode 100644 scripts/niss-misc/find-parent/find-parent create mode 100644 scripts/niss-misc/galleryHelpers/make-info create mode 100644 scripts/niss-misc/galleryHelpers/make-sizes create mode 100644 scripts/niss-misc/nd/nd diff --git a/flake.nix b/flake.nix index 4fee284..f3b6081 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,8 @@ "firefox-esr-alias" "inkscape-xwayland" ]; + crossPlatformPackages = packagesInDir "scripts" [ "niss-misc" ]; + fonts = packagesInDir "fonts" [ "constructium" "fairfax-hd" "kreative-square" "muller" "pragmatapro" "teranoptia" @@ -35,7 +37,7 @@ ifLinux64 = f: pkgs: optionalAttrs (pkgs.system == "x86_64-linux") (f pkgs); mkPackages = pkgs: - ifLinux64 linuxPackages pkgs // fonts pkgs; + ifLinux64 linuxPackages pkgs // crossPlatformPackages pkgs // fonts pkgs; mkApps = pkgs: ifLinux64 linuxApps pkgs; in { diff --git a/scripts/niss-misc.nix b/scripts/niss-misc.nix new file mode 100644 index 0000000..2f2a764 --- /dev/null +++ b/scripts/niss-misc.nix @@ -0,0 +1,53 @@ +{ pkgs ? import {}, + charFilters ? true, + find-parent ? true, + galleryHelpers ? true, + nd ? true +}@inputs: + +let + inherit (pkgs) lib stdenv; + + deps = with pkgs; + (lib.filterAttrs (k: v: inputs.${k} or true) { + galleryHelpers = [ rakudo ]; + charFilters = [ rakudo ]; + find-parent = [ execline ]; + nd = [ execline ]; + }); + + dir = d: + let path = ./niss-misc/${d}; in + lib.fileset.toSource { root = path; fileset = path; }; +in + assert lib.assertMsg (nd -> find-parent) "nd depends on find-parent"; + + stdenv.mkDerivation { + name = "niss-misc"; + + buildInputs = lib.concatLists (lib.attrValues deps); + + dontUnpack = true; + + installPhase = lib.concatStringsSep "\n" + ([ "mkdir -p $out/bin" ] ++ + map (d: "cp ${dir d}/* $out/bin") (lib.attrNames deps)); + + meta = { + description = "niss's funny little scripts"; + longDescription = '' + - `galleryHelpers`: helper scripts for putting together the input for + `gallery.niss.website`. exceedingly unlikely to be useful to anyone + else. + - `charFilters`: `little` for making text ˡⁱᵏᵉ ᵗʰⁱˢ and `wide` for + text like this. + - find-parent: + `find-parent ‹file›` looks for `‹file›` in the current directory or + any of its ancestors, and prints the path of the file found, if any. + `find-parent -d ‹file›` prints the path of only the directory + containing the file, rather than the file itself. + - nd: looks for a `flake.nix` and runs `nix develop` if so. + uses `find-parent`. + ''; + }; + } diff --git a/scripts/niss-misc/charFilters/little b/scripts/niss-misc/charFilters/little new file mode 100644 index 0000000..29af371 --- /dev/null +++ b/scripts/niss-misc/charFilters/little @@ -0,0 +1,57 @@ +#!/usr/bin/env raku + +#| print arguments or stdin as superscript +unit sub MAIN(*@args); + +# char ranges {{{ +my %uc = ( + A => 'ᴬ', B => 'ᴮ', D => 'ᴰ', E => 'ᴱ', G => 'ᴳ', H => 'ᴴ', I => 'ᴵ', + J => 'ᴶ', K => 'ᴷ', L => 'ᴸ', M => 'ᴹ', N => 'ᴺ', O => 'ᴼ', P => 'ᴾ', + R => 'ᴿ', T => 'ᵀ', U => 'ᵁ', W => 'ᵂ' +); +my %lc = ( + a => 'ᵃ', b => 'ᵇ', c => 'ᶜ', d => 'ᵈ', e => 'ᵉ', f => 'ᶠ', + g => 'ᵍ', h => 'ʰ', i => 'ⁱ', j => 'ʲ', k => 'ᵏ', l => 'ˡ', + m => 'ᵐ', n => 'ⁿ', o => 'ᵒ', p => 'ᵖ', r => 'ʳ', s => 'ˢ', + t => 'ᵗ', u => 'ᵘ', v => 'ᵛ', w => 'ʷ', x => 'ˣ', y => 'ʸ', + z => 'ᶻ' +); +my %num = ( + 0 => '⁰', 1 => '¹', 2 => '²', 3 => '³', + ([4..9] Z ['⁴'..'⁹']).Map +); +my %other = ('!' => 'ꜝ', '.' => '·', '(' => '⁽', ')' => '⁾'); +# }}} +my %caseless = %lc, %num, %other; +my %cased = %uc, %caseless; + + +sub rejects(@chars, %map) { @chars.grep(* ∉ %map).unique.join(', ') } +sub non-ws($str) { $str.comb.grep(* !~~ /\s/) } + +sub shrink(Str $str) { + my $str-lc = lc $str; + my @chars = non-ws $str; + my @lc-chars = non-ws $str-lc; + + my $cased-rejects = rejects @chars, %cased; + my $caseless-rejects = rejects @lc-chars, %caseless; + + if !$cased-rejects { + $str.trans: %cased + } elsif !$caseless-rejects { + note "using lowercase because no superscripts for $cased-rejects"; + $str-lc.trans: %caseless; + } else { + note "no superscripts for $caseless-rejects"; + "" + } +} + +if @args > 0 { + say shrink @args.join: ' ' +} else { + say shrink $_ for $*IN.lines +} + +# vim: set ft=little fdm=marker : diff --git a/scripts/niss-misc/charFilters/wide b/scripts/niss-misc/charFilters/wide new file mode 100644 index 0000000..299c564 --- /dev/null +++ b/scripts/niss-misc/charFilters/wide @@ -0,0 +1,19 @@ +#!/usr/bin/env raku + +#| convert arguments or stdin to fullwidth characters +unit sub MAIN(*@args); + +sub widen(Str $str) { + my %map = (['!'..'~'] Z ['!'..'~']).Map, ' ' => ' '; + my @rejects = $str.comb.grep: {$^x !~~ /\s/ && $^x ∉ %map}; + note "unknown characters: @rejects.join(', ')" if @rejects; + $str.trans: %map +} + +if @args > 0 { + say widen @args.join: ' ' +} else { + say widen $_ for $*IN.lines +} + +# vim: set ft=raku : diff --git a/scripts/niss-misc/find-parent/find-parent b/scripts/niss-misc/find-parent/find-parent new file mode 100644 index 0000000..5d0614d --- /dev/null +++ b/scripts/niss-misc/find-parent/find-parent @@ -0,0 +1,32 @@ +#!/usr/bin/env execlineb + +elgetopt d +elgetpositionals +importas -D "" dironly ELGETOPT_d + +if -nt { test $# -eq 0 } + +define FOUND 69 +foreground { + # look for each pattern (in cwd) + forx -E -x$FOUND pat { $@ } + # if a file exists, exit with $FOUND, which breaks the loop + elglob -0 files $pat + forx -E -x$FOUND file { $files } + ifthenelse { test -n $dironly } { pwd } { readlink -f $file } + exit $FOUND +} +importas result ? + +# if the loop exited with $FOUND then we're done +if -nt { test $result -eq $FOUND } +# if we're at / give up +if -n -x1 { + getcwd -E cwd + test $cwd = / +} + +cd .. +ifelse { test -n $dironly } { find-parent -d $@ } find-parent $@ + +# vim: set ft=execline : diff --git a/scripts/niss-misc/galleryHelpers/make-info b/scripts/niss-misc/galleryHelpers/make-info new file mode 100644 index 0000000..ac72aed --- /dev/null +++ b/scripts/niss-misc/galleryHelpers/make-info @@ -0,0 +1,67 @@ +#!/usr/bin/env raku + +#| generate gallery info file +unit sub MAIN( + Str :o($output) = 'info.yaml', #= output file + Bool :f($force) = False, #= overwrite an existing file + Bool :N($no-edit) = False, #= don't open editor after + *@files, #= files to list (default all images except thumb.*) +); + +my $cwd = $*CWD.basename; +my token isodate { + $ = [$=[\d**4] '-' $=[\d**2] '-' $=[\d**2]] +} + +my $date = $cwd.match(&isodate) // Date.today.yyyy-mm-dd; +my $name = $*CWD.basename.subst(/^ <[\w]>*\-/, '').trans('-' => ' '); + +sub image($path) { + qq:to/END/.chomp + - path: $path.basename() + desc: > + ... + nsfw: false + warning: "" + END +} + +sub is-image($_) { /\. [jpg | png | webp | gif | svg]$/ and not /^thumb\./ } + +my @images = @files ?? @files».IO !! $*CWD.dir(test => &is-image).sort; + +die "no images found in $*CWD" unless @images; + +my $thumb-line = do given $*CWD.dir: test => /^thumb \./ { + when ([*]) { "thumb: $_[0].basename()" } + default { "" } +}; + +# spurt 'info.yaml', qq:to/END/; +my $yaml = qq:to/END/; + date: $date + title: $name + + tags: null + + images: + {@images.map(&image).join: "\n"} + $thumb-line + bg: noborder + + desc: | + ... + END + +if $output eq '-' { + print $yaml; +} +elsif $output.IO.e and not $force { + say "$output already exists, use -f if you're sure"; + exit 1; +} else { + spurt $output, $yaml; +} + +my $editor = %*ENV // %*ENV // 'nano'; +run $editor, $output unless $no-edit; diff --git a/scripts/niss-misc/galleryHelpers/make-sizes b/scripts/niss-misc/galleryHelpers/make-sizes new file mode 100644 index 0000000..6cc0e7c --- /dev/null +++ b/scripts/niss-misc/galleryHelpers/make-sizes @@ -0,0 +1,147 @@ +#!/usr/bin/env raku + +#| resize images for uploading to gallery sites +unit sub MAIN( + #| number of concurrent jobs (default min{10, # cores}) + Int :j(:$jobs) = min(10, $*KERNEL.cpu-cores), + #| formats to use for mastodon (png, webp, jpg, default, skip) + :m(:$mastodon) = Nil, + #| formats to use for furaffinity/weasyl + :f(:$furaffinity) = Nil, + #| formats to use for gallery.n.w + :g(:$gallery) = Nil, + #| formats to use for bluesky + :b(:$bluesky) = Nil, + #| webp/jpg quality setting (default 98) + Int :Q(:$quality) = 98, + #| only run conversions explicitly mentioned + Bool :O(:$only) = False, + #| don't actually do anything (implies -v) + Bool :n(:$dry-run) = False, + #| print every action + Bool :v(:$verbose) = False, + #| out dir (default ./out) + IO(Str) :o(:$outdir) = 'out'.IO, + #| output as e.g. thing.gallery.webp instead of gallery/thing.webp + Bool :F(:$flat) = False, + #| background colour to use for jpg + Str :B(:$background) = 'white', + #| files to resize (default *.jpg *.png *.webp) + *@files, +); + +sub cmd(*@args) { + say "> @args[]" if $dry-run || $verbose; + run @args unless $dry-run; +} + +sub verbose(*@args) { say "> @args[]" if $verbose } + +# fixup args + +enum Format ; + +sub to-formats($_, *@default) { + when 'skip' { Empty } + when 'default' { @default } + when Nil { $only ?? Empty !! @default } + when Format { $_ } + default { + .lc.split(',').map: { Format::{$_} // die "unrecognised format '$_'" } + } +} + + +my @mastodon = to-formats $mastodon, webp; +# my @itaku = to-formats $itaku, webp; +my @furaffinity = to-formats $furaffinity, png; +my @gallery = to-formats $gallery, png,webp; +my @bluesky = to-formats $bluesky, jpg; + +die "furaffinity/weasyl don't know what webp are" if webp ∈ @furaffinity; +die "at the time of writing, bsky converts everything to jpg" + if @bluesky and @bluesky !eqv [jpg]; + +if @files { @files .= map(*.IO) } +else { @files = $*CWD.dir: test => /:i \.[jpe?g|png|webp]$/ && *.IO.f } +for @files { die "$_ is not a regular file" unless .f; } + + +# running imagemagick (and oxipng sometimes) 🪄 + +sub make-filename($in, $base, $format) { + $flat ?? $outdir.child($in.basename).extension("$base.$format") + !! $outdir.child($base).child($in.basename).extension("$format") +} + + +multi convert($in, @names where .elems >= 1, $size, $format) { + my $name = @names[0]; + my $out = make-filename($in, $name, $format); + my @bg-flags = «-background $background -alpha remove» + if $format == jpg or $name eq 'bluesky'; + cmd , $out.dirname; + cmd , $in, '-resize', $size, '-quality', $quality, @bg-flags, $out; + cmd , $out if $format == png; + + for @names[1..*] { + my $link = make-filename($in, $_, $format); + cmd , $link.dirname; + cmd , $out, $link; + } + + $out +} +multi convert($in, $name, $size, $format) { + convert($in, [$name], $size, $format); +} + +sub square($s) { "{$s}x{$s}>" } +sub pixels($s) { "{$s}@" } + +my @sites = [ + {:name«fedi itaku», formats => @mastodon, size => square 2800}, + {:name, formats => @furaffinity, size => square 1920}, + {:name, formats => @furaffinity, size => square 2800}, + {:name, formats => @gallery, size => square 3000}, + {:name, formats => @bluesky, size => square 2000}, +]; + + +# go go go + +my @tasks; + +sub task-serial(&body, :$if = True) { body if $if; } + +sub task-parallel(&body, :$if = True) { + state $sem = Semaphore.new: $jobs; + return unless $if; + my $task = start { ENTER $sem.acquire; LEAVE $sem.release; body } + @tasks.push: $task; + $task +} + +constant &task = $dry-run ?? &task-serial !! &task-parallel; + +my @todo = (@files X @sites).map: { hash :file(.[0]), |.[1] } +my $len = @todo.map(*.).sum; +my $index; + +for @todo -> (:$file, :$name, :@formats, :$size) { + my @outputs = @formats.map: -> $format { + my $i = ++$index; my $base = $file.basename; + task { + say "[$i/$len] $name $base ($format)"; + convert $file, $name, $size, $format; + } + } + + task if => !$dry-run && @outputs > 1, { + my $keep = @outputs».=result.map({$_ => .s}).min(*.value).key; + verbose "# keeping $keep"; + for @outputs { next when $keep; cmd "rm", $_ } + } +} + +@tasks».result; diff --git a/scripts/niss-misc/nd/nd b/scripts/niss-misc/nd/nd new file mode 100644 index 0000000..85fea98 --- /dev/null +++ b/scripts/niss-misc/nd/nd @@ -0,0 +1,19 @@ +#!/usr/bin/env execlineb + +importas -D /bin/bash default_shell SHELL + +elgetopt s: +importas -D $default_shell shell ELGETOPT_s +elgetpositionals + +getcwd -E cwd +backtick -D "" -E nixdir { find-parent -d flake.nix } +ifelse { test $nixdir = "" } { + foreground { echo "flake.nix not found in parents of ${cwd}" } + exit 1 +} + +cd $nixdir +nix develop --no-warn-dirty $@ -c execlineb -c "cd ${cwd} ${shell}" + +# vim: set ft=execline :