]> Sergey Matveev's repositories - zk.git/commitdiff
Move to Perl
authorSergey Matveev <stargrave@stargrave.org>
Tue, 6 May 2025 12:06:36 +0000 (15:06 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 6 May 2025 12:10:19 +0000 (15:10 +0300)
CACHE [deleted file]
README [deleted file]
zk [new file with mode: 0755]
zk.zsh [deleted file]

diff --git a/CACHE b/CACHE
deleted file mode 100644 (file)
index fb583ef..0000000
--- a/CACHE
+++ /dev/null
@@ -1,13 +0,0 @@
-Cache directory resembles the notes hierarchy. Each file corresponds to
-the note file, keeping at least three lines: versioned magic number,
-inode number and ctime value in seconds. All of that is used to
-determine if note was changed and cache is valid.
-
-All other lines contain the links present in the note.
-
-Any change to the file leads to ctime changing and cache invalidation
-with full parsing of the note.
-
-If note's cache is valid, then also the part of HTML is also kept in
-.html-file nearby. It contains all the data till closing <pre>-tag.
-Links and backlinks are generated every time.
diff --git a/README b/README
deleted file mode 100644 (file)
index 1dfb2c7..0000000
--- a/README
+++ /dev/null
@@ -1,44 +0,0 @@
-zk.zsh -- zettelkästen/wiki/static website helper/generator
-
-* Use plain text files. One file per note/record
-* Use CamelCase names, that are friendly with vi editor word
-  boundary determination
-* You can safely place that notes in subdirectories hierarchy
-* Edit and view your notes from their root path (zettelkästen
-  root). That way if you press "gf" on "Foo/Bar/Baz" word in
-  Vim, then it will open Foo/Bar/Baz file in current window
-* Use expand("%:r") function to get current file path relative
-  to the root
-* Place links to another notes inside square brackets. You can
-  highlight them with:
-    syntax region zkLink start="\[\S" end="\S]"
-    highlight link zkLink String
-* Link to the directory's index can be made with [Dir/]
-* Use Vim's filename completion (:help compl-filename) to
-  complete directories and filenames in them
-* fzf (https://github.com/junegunn/fzf) related tools can be
-  used to navigate among existing notes
-* Ordinary grep, git-jump or similar tools can be used to search
-  and quickly open results in the editor
-
-The only thing Vim lacks there is ability to tell who backlinks
-to the specified page. zk.zsh can be used to show what pages
-backlinks to specified page and what pages are referenced by it:
-    $ zk.zsh links some/page
-    Another/Page
-    SomePage
-    $ zk.zsh backs some/page
-    [...]
-That can be used to make categories and tags on notes. If note
-contains a link to category/tag (even an empty file), then it
-will be backlinked.
-
-    $ zk.zsh htmls path/to/dir
-Will convert all your notes to HTMLs with properly created links
-to other pages. It also will include all backlinks in them. Each
-directory will also contain index page with links to all
-existing pages in current directory and to subdirectories.
-
-If ZK_CACHE environment variable contains path to some
-directory, then it will keep caching information for speeding up
-the processes in it. Look for CACHE file for more information.
diff --git a/zk b/zk
new file mode 100755 (executable)
index 0000000..5f1fe40
--- /dev/null
+++ b/zk
@@ -0,0 +1,307 @@
+#!/usr/bin/env perl
+# zk -- zettelkästen/wiki/static website helper/generator
+# Copyright (C) 2022-2025 Sergey Matveev <stargrave@stargrave.org>
+
+=pod
+
+=encoding utf-8
+
+=head1 SYNOPSIS
+
+zk -- zettelkästen/wiki/static website helper/generator
+
+=head1 USAGE
+
+=over
+
+=item *
+
+Use plain text files. One file per note/record.
+
+=item *
+
+Use CamelCase names, that are friendly with vi editor word boundary
+determination.
+
+=item *
+
+You can safely place that notes in subdirectories hierarchy.
+
+=item *
+
+Edit and view your notes from their root path (zettelkästen root). That
+way if you press C<gf> on F<Foo/Bar/Baz> word in Vim, then it will open
+F<Foo/Bar/Baz> file in current window.
+
+=item *
+
+Use C<expand("%:r")> function to get current file path relative to the root.
+
+=item *
+
+Place links to another notes inside square brackets.
+You can highlight them with:
+
+    syntax region zkLink start="\[\S" end="\S]"
+    highlight link zkLink String
+
+=item *
+
+Link to the directory's index can be made with C<[Dir/]>.
+
+=item *
+
+Use Vim's filename completion (C<:help compl-filename>) to complete
+directories and filenames in them.
+
+=item *
+
+L<fzf|https://github.com/junegunn/fzf> related tools can be used to
+navigate among existing notes.
+
+=item *
+
+Ordinary C<grep>, C<git-jump> or similar tools can be used to search and
+quickly open results in the editor.
+
+=back
+
+The only thing Vim lacks there is ability to tell who backlinks
+to the specified page. zk can be used to show what pages backlinks to
+specified page and what pages are referenced by it:
+
+    $ zk links some/page
+    Another/Page
+    SomePage
+    $ zk backs some/page
+    [...]
+
+That can be used to make categories and tags on notes. If note contains
+a link to category/tag (even an empty file), then it will be backlinked.
+
+    $ zk htmls path/to/dir
+
+Will convert all your notes to HTMLs with properly created links
+to other pages. It also will include all backlinks in them. Each
+directory will also contain index page with links to all
+existing pages in current directory and to subdirectories.
+
+Older version of that script, written on Z shell, can be found in Git history.
+
+=cut
+
+use strict;
+use warnings;
+
+sub usage {
+    print STDERR "Usage:
+\t$0 links PAGE
+\t$0 backs PAGE
+\t$0 htmls DIR\n";
+    exit 1;
+}
+
+usage if $#ARGV == -1;
+
+my %mtimes;
+my %sizes;
+my %cats;
+
+{
+    use File::Find;
+    use POSIX qw(strftime);
+    sub wanted {
+        my $fn = $_;
+        my $pth = $File::Find::name;
+        $pth =~ s/^\.\/?//;
+        if (-d $fn) {
+            opendir(my $dh, $fn) or die "$!";
+            my @entries;
+            while (readdir $dh) {
+                next if /^\./;
+                if (-d "$fn/$_") {
+                    $_ .= "/";
+                }
+                push @entries, $_;
+            }
+            closedir $dh;
+            $cats{$pth} = \@entries;
+        } else {
+            die "unacceptable filename: $pth" if $_ eq "index";
+            my @s = stat($fn) or die "$!";
+            $sizes{$pth} = $s[7];
+            $mtimes{$pth} = strftime "%Y-%m-%d %H:%M:%S", gmtime $s[9];
+        }
+    }
+    find(\&wanted, ".");
+}
+
+my %links;
+my %backs;
+for my $pth (keys %mtimes) {
+    my @ws;
+    open(my $fh, "<", $pth) or die "$!";
+    while (<$fh>) {
+        foreach my $w (split /\s+/) {
+            next unless $w =~ /\[([^]]+)\]/;
+            $w = $1;
+            if ($w =~ /\/$/) {
+                my $w = substr $w, 0, -1;
+                next unless exists $cats{$w};
+            } else {
+                next unless exists $mtimes{$w};
+            }
+            push @ws, $w;
+        }
+    }
+    close $fh;
+    @ws = sort @ws;
+    next if $#ws == -1;
+    $links{$pth} = \@ws;
+    foreach (@ws) {
+        if (not defined $backs{$_}) {
+            my %h;
+            $backs{$_} = \%h;
+        }
+        $backs{$_}{$pth} = 1;
+    }
+}
+
+sub startBody {
+    my $out = shift;
+    my $title = shift;
+    print $out "<!DOCTYPE html>
+<html><head>
+<title>$title</title>
+<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
+</head><body>";
+}
+
+use File::Spec;
+
+sub genHTML {
+    my $out = shift;
+    my $page = shift;
+    my @lnks = defined $links{$page} ? @{$links{$page}} : ();
+    my @rels;
+    my $rel;
+    foreach (@lnks) {
+        $rel = File::Spec->abs2rel($_, $page);
+        $rel = substr $rel, 3;
+        if (-d $rel) {
+            $rel .= "/index";
+        }
+        push @rels, $rel;
+    }
+    startBody $out, $page;
+    print $out "<pre>\n";
+    open(my $fh, "<", $page) or die "$!";
+    while (<$fh>) {
+        s/&/\&amp;/g;
+        s/</\&lt;/g;
+        s/>/\&gt;/g;
+        my $rel;
+        while (my ($i, $l) = each @lnks) {
+            s/\[$l\]/<a href="$rels[$i].html">[$l]<\/a>/g;
+        }
+        print $out $_;
+    }
+    close $fh;
+    print $out "</pre>\n";
+    if ($#lnks != -1) {
+        print $out "<table border=1><caption>Links</caption>\n";
+        my $mtime;
+        while (my ($i, $l) = each @lnks) {
+            $mtime = (defined $mtimes{$l}) ? defined $mtimes{$l} : "";
+            print $out "<tr><td><a href=\"$rels[$i].html\">$l</a></td>
+    <td><tt>$mtime</tt></td></tr>\n";
+        }
+        print $out "</table>\n";
+    }
+    @lnks = sort keys %{$backs{$page}};
+    if ($#lnks != -1) {
+        print $out "<table border=1><caption>Backlinks</caption>\n";
+        foreach my $l (@lnks) {
+            $rel = File::Spec->abs2rel($l, $page);
+            $rel = substr $rel, 3;
+            print $out "<tr><td><a href=\"$rel.html\">$l</a></td>
+    <td><tt>$mtimes{$l}</tt></td></tr>\n";
+        }
+        print $out "</table>\n";
+    }
+    print $out "</body></html>\n";
+}
+
+sub genIndex {
+    my $out = shift;
+    my $page = shift;
+    startBody $out, "$page/";
+    print $out "<table border=1>\n";
+    my @lnks = sort @{$cats{$page}};
+    foreach my $l (@lnks) {
+        next if $l =~ /\/$/;
+        my $pth = ($page eq "") ? $l : "$page/$l";
+        print $out "<tr><td><a href=\"$l.html\">$pth</a></td>
+    <td><tt>$mtimes{$pth}</tt></td><td>$sizes{$pth} B</td></tr>\n";
+    }
+    print $out "</table>\n";
+    @lnks = grep { /\/$/ } @lnks;
+    if ($#lnks != -1) {
+        print $out "<table border=1><caption>Subcategories</caption>\n";
+        foreach my $l (@lnks) {
+            $l = substr $l, 0, -1;
+            my $pth = ($page eq "") ? $l : "$page/$l";
+            my @entries = @{$cats{$pth}};
+            my $ctr = 1 + $#entries;
+            print $out "<tr><td><a href=\"$l/index.html\">$pth</a></td>
+    <td>$ctr</td></tr>\n"
+        }
+        print $out "</table>\n";
+    }
+    @lnks = sort keys %{$backs{"$page/"}};
+    if ($#lnks != -1) {
+        print $out "<table border=1><caption>Backlinks</caption>\n";
+        my $rel;
+        foreach my $l (@lnks) {
+            $rel = File::Spec->abs2rel($l, $page);
+            print $out "<tr><td><a href=\"$rel.html\">$l</a></td>
+    <td><tt>$mtimes{$l}</tt></td><td>$sizes{$l} B</td></tr>\n";
+        }
+        print $out "</table>\n";
+    }
+    print $out "</body></html>\n"
+}
+
+if ($ARGV[0] eq "dump") {
+    use Data::Dumper;
+    print Data::Dumper->Dump([\%links, \%backs, \%cats], [qw(*links *backs *cats)]);
+} elsif ($ARGV[0] eq "links") {
+    map { print "$_\n" } @{$links{$ARGV[1]}};
+} elsif ($ARGV[0] eq "backs") {
+    map { print "$_\n" } sort keys %{$backs{$ARGV[1]}};
+} elsif ($ARGV[0] eq "html") {
+    genHTML \*STDOUT, $ARGV[1];
+} elsif ($ARGV[0] eq "html-index") {
+    genIndex \*STDOUT, $ARGV[1];
+} elsif ($ARGV[0] eq "htmls") {
+    my $now = time;
+    use File::Path qw(make_path);
+    foreach my $cat (keys %cats) {
+        make_path "$ARGV[1]/$cat";
+        my $fn = "$ARGV[1]/$cat/index.html";
+        open(my $fh, ">", $fn) or die "$!";
+        genIndex $fh, $cat;
+        close $fh;
+        utime $now, $now, $fn;
+    }
+    my @s;
+    foreach my $pth (keys %mtimes) {
+        open(my $fh, ">", "$ARGV[1]/$pth.html") or die "$!";
+        genHTML $fh, $pth;
+        close $fh;
+        @s = stat($pth) or die "$!";
+        utime $s[9], $s[9], "$ARGV[1]/$pth.html";
+    }
+} else {
+    usage;
+}
diff --git a/zk.zsh b/zk.zsh
deleted file mode 100755 (executable)
index 0f7c118..0000000
--- a/zk.zsh
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/env zsh
-# zk.zsh -- zettelkästen/wiki/static website helper/generator
-# Copyright (C) 2022-2025 Sergey Matveev <stargrave@stargrave.org>
-
-setopt ERR_EXIT
-ZK_VERSION=ZKZSH1
-
-usage() {
-    >&2 <<EOF
-Usage:
-  \$ $0:t links PAGE
-    Print PAGE's links
-  \$ $0:t backs PAGE
-    Print PAGE's backlinks
-  \$ $0:t htmls DIR
-    Generate HTMLs in DIR
-EOF
-    exit 1
-}
-
-[[ $# -eq 2 ]] || usage
-
-setopt GLOB_STAR_SHORT
-zmodload -F zsh/stat b:zstat
-typeset -A mtimes
-typeset -A sizes
-for p (**(.)) {
-    [[ $p:t == "index" ]] && {
-        echo unacceptable filename: $p >&2
-        exit 1
-    }
-    zstat -A mtime -F "%F %T" +mtime $p
-    zstat -A size +size $p
-    mtimes[$p]=${mtime[1]}
-    sizes[$p]=${size[1]}
-}
-typeset -A cats
-for p (**(/)) {
-    local files=($p/*(N))
-    cats[$p]=$#files
-}
-
-zmodload zsh/mapfile
-zmodload -F zsh/files b:zf_mkdir
-typeset -A links
-typeset -A backs
-typeset -A cached
-typeset -aU ws
-for p (${(k)mtimes}) {
-    [[ $ZK_CACHE ]] && {
-        zstat -A inode +inode $p
-        zstat -A ctime +ctime $p
-        cache=(${(f)mapfile[$ZK_CACHE/$p]})
-        if [[ ( ${cache[1]} = $ZK_VERSION ) &&
-              ( ${cache[2]} = ${inode[1]} ) &&
-              ( ${cache[3]} = ${ctime[1]} ) ]]; then
-            ws=(${cache[4,-1]})
-            [[ $ws ]] && links[$p]=${(j: :)ws}
-            cached[$p]=1
-            continue
-        fi
-    }
-    ws=()
-    for w (${=mapfile[$p]}) {
-        [[ $w =~ "\[([^] ]+)\]" ]] || continue
-        w=${match[1]}
-        [[ ( $w =~ "/$" ) && ( ${cats[$w[1,-2]]} ) ]] && {
-            ws=($ws $w)
-            continue
-        }
-        [[ ${mtimes[$w]} ]] || {
-            [[ $ZK_SHOW_MISSING ]] && print "missing $w"
-            continue
-        }
-        ws=($ws $w)
-    }
-    [[ $ZK_CACHE ]] && {
-        zf_mkdir -p $ZK_CACHE/$p:h
-        ws=($ZK_VERSION ${inode[1]} ${ctime[1]} $ws)
-        print -l $ws >$ZK_CACHE/$p
-        ws=(${ws[4,-1]})
-    }
-    [[ $ws ]] && links[$p]=${(j: :)ws}
-}
-unset cache ws
-for p ws (${(kv)links}) {
-    for w (${=ws}) backs[$w]="$p ${backs[$w]}"
-}
-for p ws (${(kv)backs}) backs[$p]=${(j: :)${(u)=ws}}
-
-getrel() {
-    # nearly the copy-paste of Functions/Misc/relative
-    local dst=$2:a
-    local src=$1:h:a
-    local -a cur abs
-    cur=(${(s:/:)src})
-    abs=(${(s:/:)dst:h} $dst:t)
-    integer i=1
-    while [[ i -le $#abs && $abs[i] == $cur[i] ]] ; do
-        ((++i > $#cur)) && {
-            REPLY=${(j:/:)abs[i,-1]}
-            return
-        }
-    done
-    src=${(j:/:)cur[i,-1]/*/..}
-    dst=${(j:/:)abs[i,-1]}
-    REPLY=$src${dst:+/$dst}
-}
-
-genHTML() {
-    local page=$1
-    local data p
-    [[ $# -eq 1 ]] && data=${mapfile[$page]} || data=$2
-    local _links=(${(oi)=links[$page]})
-    if [[ ( ${cached[$page]} ) && ( -s $ZK_CACHE/${page}.html ) ]]; then
-        <$ZK_CACHE/${page}.html
-    else
-        data=${data//&/&amp;}
-        data=${data//</&lt;}
-        data=${data//>/&gt;}
-        for p ($_links) {
-            getrel $page $p
-            [[ -d $p ]] && REPLY=$REPLY/index
-            data="${data//\[${p}\]/<a href=\"${REPLY}.html\">[$p]</a>}"
-        }
-        data="<!DOCTYPE html>
-<html><head>
-<title>$page</title>
-<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
-</head><body><pre>
-$data</pre>"
-        if [[ $ZK_CACHE ]]; then
-            print -r "$data" >$ZK_CACHE/${page}.html
-            <$ZK_CACHE/${page}.html
-        else
-            print -r "$data"
-        fi
-    fi
-    if [[ $_links ]]; then
-        print "<table border=1><caption>Links</caption>"
-        for p ($_links) {
-            getrel $page $p
-            [[ -d $p ]] && REPLY=$REPLY/index
-            print "<tr><td><a href=\"${REPLY}.html\">$p</a></td><td><tt>${mtimes[$p]}</tt></td></tr>"
-        }
-        print "</table>"
-    fi
-    local bs=(${(oi)=${backs[$page]}})
-    if [[ $bs ]]; then
-        print "<table border=1><caption>Backlinks</caption>"
-        for p ($bs) {
-            getrel $page $p
-            print "<tr><td><a href=\"${REPLY}.html\">$p</a></td><td><tt>${mtimes[$p]}</tt></td></tr>"
-        }
-        print "</table>"
-    fi
-    print "</body></html>"
-}
-
-zmodload -F zsh/datetime b:strftime
-strftime -s now "%F %T"
-
-genIndex() {
-    local p
-    local entries=()
-    local _links=()
-    typeset -aU _cats=()
-    local curdepth=${#${(s:/:)1}}
-    (( curdepth = curdepth + 1 ))
-    for p (${(k)mtimes[(I)$1*]}) {
-        case ${#${(As:/:)p}} in
-        ($curdepth) _links=($p $_links) ;;
-        ( $(( $curdepth + 1 )) ) _cats=(${1}${${p#$1}%%/*} $_cats) ;;
-        (*) continue ;;
-        esac
-    }
-    local page=${1}index
-    print "<!DOCTYPE html>
-<html><head>
-<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
-<title>$page</title>
-</head><body><table border=1>"
-    for p (${(oi)_links}) {
-        getrel $page $p
-        print "<tr><td><a href=\"${REPLY}.html\">$p</a></td><td><tt>${mtimes[$p]}</tt></td><td>${sizes[$p]} B</td></tr>"
-    }
-    print "</table>"
-    if [[ $_cats ]]; then
-        print "<table border=1><caption>Subcategories</caption>"
-        for p (${(oi)_cats}) {
-            getrel $page $p/index
-            print "<tr><td><a href=\"${REPLY}.html\">$p</a></td><td>${cats[$p]}</td></tr>"
-        }
-        print "</table>"
-    fi
-    local bs=(${(oi)=${backs[$1]}})
-    if [[ $bs ]]; then
-        print "<table border=1><caption>Backlinks</caption>"
-        for p ($bs) {
-            getrel $page $p
-            print "<tr><td><a href=\"${REPLY}.html\">$p</a></td><td><tt>${mtimes[$p]}</tt></td></tr>"
-        }
-        print "</table>"
-    fi
-    print "</body></html>"
-}
-
-case $1 in
-(links) for w (${(oi)=${links[$2]}}) print $w ;;
-(backs) for w (${(oi)=${backs[$2]}}) print $w ;;
-(html) genHTML $2 ;;
-(html-index) genIndex $2 ;;
-(htmls)
-    for p (${(k)mtimes}) {
-        zf_mkdir -p $2/$p:h
-        genHTML $p >$2/$p.html
-        touch -r $p $2/$p.html
-    }
-    for p (${(k)cats}) genIndex $p/ >$2/$p/index.html
-    genIndex "" >$2/index.html
-    for p ("" ${(k)cats}) touch -d ${now/ /T} $2/$p/index.html
-    ;;
-(*) usage ;;
-esac