From: Sergey Matveev Date: Tue, 6 May 2025 12:06:36 +0000 (+0300) Subject: Move to Perl X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=fa0d78f0f9580d191ffb92cf5744be9c7d178885;p=zk.git Move to Perl --- diff --git a/CACHE b/CACHE deleted file mode 100644 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
-tag.
-Links and backlinks are generated every time.
diff --git a/README b/README
deleted file mode 100644
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
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 
+
+=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 on F word in Vim, then it will open
+F file in current window.
+
+=item *
+
+Use C 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 related tools can be used to
+navigate among existing notes.
+
+=item *
+
+Ordinary C, C 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 "
+
+$title
+
+";
+}
+
+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 "
\n";
+    open(my $fh, "<", $page) or die "$!";
+    while (<$fh>) {
+        s/&/\&/g;
+        s//\>/g;
+        my $rel;
+        while (my ($i, $l) = each @lnks) {
+            s/\[$l\]/[$l]<\/a>/g;
+        }
+        print $out $_;
+    }
+    close $fh;
+    print $out "
\n"; + if ($#lnks != -1) { + print $out "\n"; + my $mtime; + while (my ($i, $l) = each @lnks) { + $mtime = (defined $mtimes{$l}) ? defined $mtimes{$l} : ""; + print $out " + \n"; + } + print $out "
Links
$l$mtime
\n"; + } + @lnks = sort keys %{$backs{$page}}; + if ($#lnks != -1) { + print $out "\n"; + foreach my $l (@lnks) { + $rel = File::Spec->abs2rel($l, $page); + $rel = substr $rel, 3; + print $out " + \n"; + } + print $out "
Backlinks
$l$mtimes{$l}
\n"; + } + print $out "\n"; +} + +sub genIndex { + my $out = shift; + my $page = shift; + startBody $out, "$page/"; + print $out "\n"; + my @lnks = sort @{$cats{$page}}; + foreach my $l (@lnks) { + next if $l =~ /\/$/; + my $pth = ($page eq "") ? $l : "$page/$l"; + print $out " + \n"; + } + print $out "
$pth$mtimes{$pth}$sizes{$pth} B
\n"; + @lnks = grep { /\/$/ } @lnks; + if ($#lnks != -1) { + print $out "\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 " + \n" + } + print $out "
Subcategories
$pth$ctr
\n"; + } + @lnks = sort keys %{$backs{"$page/"}}; + if ($#lnks != -1) { + print $out "\n"; + my $rel; + foreach my $l (@lnks) { + $rel = File::Spec->abs2rel($l, $page); + print $out " + \n"; + } + print $out "
Backlinks
$l$mtimes{$l}$sizes{$l} B
\n"; + } + print $out "\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 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 - -setopt ERR_EXIT -ZK_VERSION=ZKZSH1 - -usage() { - >&2 <&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//&/&} - data=${data///>} - for p ($_links) { - getrel $page $p - [[ -d $p ]] && REPLY=$REPLY/index - data="${data//\[${p}\]/
[$p]}" - } - data=" - -$page - -
-$data
" - 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 "" - for p ($_links) { - getrel $page $p - [[ -d $p ]] && REPLY=$REPLY/index - print "" - } - print "
Links
$p${mtimes[$p]}
" - fi - local bs=(${(oi)=${backs[$page]}}) - if [[ $bs ]]; then - print "" - for p ($bs) { - getrel $page $p - print "" - } - print "
Backlinks
$p${mtimes[$p]}
" - fi - print "" -} - -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 " - - -$page -" - for p (${(oi)_links}) { - getrel $page $p - print "" - } - print "
$p${mtimes[$p]}${sizes[$p]} B
" - if [[ $_cats ]]; then - print "" - for p (${(oi)_cats}) { - getrel $page $p/index - print "" - } - print "
Subcategories
$p${cats[$p]}
" - fi - local bs=(${(oi)=${backs[$1]}}) - if [[ $bs ]]; then - print "" - for p ($bs) { - getrel $page $p - print "" - } - print "
Backlinks
$p${mtimes[$p]}
" - fi - print "" -} - -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