]> Sergey Matveev's repositories - schwabrak.git/commitdiff
Second version
authorSergey Matveev <stargrave@stargrave.org>
Wed, 19 Mar 2025 10:43:16 +0000 (13:43 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 19 Mar 2025 11:09:52 +0000 (14:09 +0300)
17 files changed:
README
add
brief [new file with mode: 0755]
cd
comment [deleted file]
comment-list [deleted file]
dep-add [deleted file]
full [new file with mode: 0755]
lib.zsh.rc [deleted file]
list [deleted file]
rc.sh [new file with mode: 0644]
recfile-export [deleted file]
recfile-export-all [deleted file]
show [deleted file]
tag-add [deleted file]
tag-list [deleted file]
uuidv7 [new file with mode: 0755]

diff --git a/README b/README
index 02db445cac1d0aaeb2471967d83776ead6bcebded2961580f8bf6bc605ce9623..3d4abc8998596c701c8b04c46c79771b1f5b713cdee23d45ebb39ae7cbe7f974 100644 (file)
--- a/README
+++ b/README
@@ -1,5 +1,8 @@
 schwabrak -- simple flat file based issue tracker
 
+Note: this is the second version/iteration/suggestion. Look for previous
+one in older commits.
+
 I want a relatively simple issue/ticket tracker. Something like Trac,
 Redmine or Fossil. But they require heavy dependencies (Python, Ruby),
 database and they are centralised. You can not work with them offline
@@ -7,84 +10,65 @@ and it is relatively complicated to replicate data to a locally running
 copy of any of those trackers (well, except for Fossil I believe). And
 is a web-interface necessary at all?
 
-What is a ticket/issue? Just some plain text descriptions, with
-attached enumerations (statuses, assignments, priorities, projects,
-subsystems, severities, resolutions, etc) and a pile of append-only
-comments, possibly with another file attachments. Can all of that
-live in a directory with several plain text files? Can it be linked
-with other issues just by making a symbolic links in deps/ subdirectory?
-Do Git commits provide supplementary metainformation about when and
-by whom any of the change is made in that directory? Definitely yes!
-And because of DVCS you get ability to keep the whole distributed copy
-of the tracker on each developer's machine. You can send changes to it
-asynchronously as a patch or bundle.
+What is a ticket/issue? Just some plain text descriptions, with attached
+enumerations (statuses, assignments, priorities, projects, subsystems,
+severities, resolutions, etc) and a pile of append-only comments,
+possibly with another file attachments. Can all of that live in a
+directory with several plain text files? Do Git commits provide
+supplementary metainformation about when and by whom any of the change
+is made in that directory? Definitely yes! And because of DVCS you get
+ability to keep the whole distributed copy of the tracker on each
+developer's machine. You can send changes to it asynchronously as a
+patch or bundle.
 
 Basically schwabrak is mainly about a convention how to keep issues in
 files, loosely similar to
-https://github.com/driusan/PoormanIssueTracker,
-https://github.com/driusan/IssueTrackerTools,
-https://github.com/aaiyer/bugseverywhere,
-https://www.youtube.com/watch?v=ysgMlGHtDMo
-from which I borrowed idea of replacing spaces with dashes in issue
-names.
-
-Issues are kept in the issues/ directory. Directory's name is the
-issue's brief name, prepended with datetime for sorting purposes. Dashes
-should be interpreted as spaces and n > 1 dashes should be interpreted
-as n-1 dashes when converting the directory name to a human readable
-issue title, as PoormanIssueTracker suggests. The "about" file contains
-the description of the issue. The "result" (initially empty) contains
-the closed issue resolution information.
-
-Each issue can have attached tags. For keeping their set in consistent
-well-defined state, the tags/ directory above the issues/ contains
-available tags for your projects.
-
-    $ for tag in status:open status:done assignee:alice assignee:bob
-        doc db test:integration severity:high severity:low ... ; do
-            touch tags/$tag
-    done
-
-It is your choice how to name and deal with them. Want to find all
-issues in done state? for i (issues/**/tags/status:done) print $i:h:h.
-This kind of information are all just enumerations.
-
-Project's name is expected to be "proj:NAME" tag for example.
-
-The deps/ subdirectory in each issue can contain symbolic links to
-another issues, referencing it. Create another kind of links between
-them as you wish.
+https://github.com/driusan/PoormanIssueTracker
+https://github.com/driusan/IssueTrackerTools
+https://github.com/aaiyer/bugseverywhere
+
+Each issue is kept in separate directory. Directory's name is the
+issue's brief name. Dashes should be interpreted as spaces and n > 1
+dashes should be interpreted as n-1 dashes when converting the directory
+name to a human readable issue title, as PoormanIssueTracker suggests.
+
+The "about" file contains the description of the issue. The "result"
+(initially empty) contains the resolved issue information. The "meta"
+file contains metainformation in recfile format, having at least "id"
+and "created" fields. The issue.rec file contains the schema of the
+records in current issues database. https://www.gnu.org/software/recutils/
 
 Want to search among the issues? Just use git grep, or ordinary grep!
 Want to search through attached PDFs or other kind of documents? You are
-free to index issues/ directory with something like recoll. Want to see
+free to index issues directory with something like recoll. Want to see
 the whole history of changes related to specific issues? Just run
-git log issues/issue-name! You can add a tag by simply touching
-issues/issues-name/tags/tag, but "tag-add" included in schwabrak creates
-symbolic links to tags/tag and checks if the tag is known beforehand,
-to keep the tags set consistent. Want to remove a tag? (git) rm
-issues/issues-name/tags/tag!
+git log issue-name!
+
+You can edit meta files either manually (it is trivial text-based
+human-friendly format) or with recset utility. Add "deps: foo-bar" field
+and press "gf" in your Vi editor to go to "foo-bar" directory of the
+dependant issue.
+
+"full" script exports the whole issues database with the prepended
+issue.rec to stdout, including "about" and "result" fields. It is
+expected to be filtered with recsel/recdel utilities.
 
 The "comment" issue's file is intended to keep the last comment related to
 the issue. By committing it you automatically accompany it with your
 (commit's author) name and the time it was added.
 
-    $ cat >issues/issues-name/comment <<EOF
+    $ cat >issues-name/comment <<EOF
     Here are my thoughts:
     * bla bla bla
     EOF
-    $ git commit issues/issues-name/comment
+    $ git commit issues-name/comment
 
 And you can view all comments/authors history later just with:
 
-    $ git log -p issues/issues-name/comment
+    $ git log -p issues-name/comment
 
-Or by using "comment-list" command included in that distribution.
-And of course you can add additional files with each commit.
-
-schwabrak includes a bunch of utilities to slightly ease dealing with
-all of that data. Personally I "hash -d" schwabrak's directory to be
-able to quickly call commands from it:
+Personally I "hash -d" schwabrak's directory to be able to quickly call
+commands from it:
 
     $ hash -d s=~/work/schwabrak
     $ cd my/issues
@@ -93,30 +77,10 @@ able to quickly call commands from it:
 To ease the creation of the new issue's directory structure, you can use
 "add" command as seen above. And yes, it is intended that "issue" and
 "name", "to", "create" can be passed as separate arguments, but
-proj/issue-name-to-create will be created as expected.
-
-All utilities strip off possible "issues/" prefix from the issues
-name, so you can easily complete issue names with something like fzf.
-"cd" utility exactly runs fzf to show you available issues, outputting
-path to selected one.
-
-"list" lists all issues in tab separated format, sorting by descending
-creation date and showing their tags. Optional arguments acts like a
-filter for the issue's tags.
-
-    $ ~s/list open stargrave !hidden
-
-That will list all issues with the tags including "status:open" AND
-"assignee:stargrave", AND excluding issues with the tags containing
-"hidden". Each argument is an expression each issue's tag is compared to
-(zsh'es "=~" test). Tag with "!"-prefix means that no tag should be in
-issue's set.
+issue-name-to-create will be created as expected.
 
-"tag-list issue" prints issue's tags if any.
-"tag-add issue [tags/]tag" adds a tag, as was noticed before.
-"dep-add issue-dst issue-src" will link issue-src in issues-dst's deps/.
-"show issue" shows most of issue's information in human friendly way.
-"comment" allows you to conveniently add comment.
+"brief" lists all issues same way as "full", but without about/result
+fields. It does not require recutils installed.
 
-comment-list, recfile-export and recfile-export-all produces
-recutils'es compatible recfile output, which is machine friendlier.
+"cd" lists all issues piped to "fzf" utility, that previews each one
+with "full" script.
diff --git a/add b/add
index e99b94af7381a9cfd801eac9bd1e385974abfc0eb9db9bb52d7a3dacf45becd0..c0188c9626ead47bcd4d0cb8c6cc370c04d55dd46b74d17db2459339fa5fc40c 100755 (executable)
--- a/add
+++ b/add
@@ -1,16 +1,14 @@
-#!/usr/bin/env zsh
+#!/bin/sh -e
 
-root=$0:h:a
-. $root/lib.zsh.rc
-
-name="$@"
-name=${name#issues/}
-name=$name:h/`print -- $name:t | endash`
-name=${name#./}
-date -u +"%Y-%m-%dT%H:%M:%S" | read created
-name=$created-$name
-print $name
-mkdir -p issues/$name/tags
-touch issues/$name/about issues/$name/result issues/$name/comment
-$EDITOR issues/$name/about
-git add issues/$name
+SROOT="$(dirname "$(realpath -- "$0")")"
+. "$SROOT/rc.sh"
+fn="$@"
+fn="$(dirname "$fn")/$(basename "$fn" | endash)"
+mkdir "$fn"
+cat >"$fn"/meta <<EOF
+id: $($SROOT/uuidv7)
+created: $(date -u +"%Y-%m-%d %H:%M:%S")
+EOF
+touch "$fn"/about "$fn"/result
+$EDITOR "$fn"/about
+git add "$fn"
diff --git a/brief b/brief
new file mode 100755 (executable)
index 0000000..6aceadc
--- /dev/null
+++ b/brief
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+
+SROOT="$(dirname "$(realpath -- "$0")")"
+. $SROOT/rc.sh
+
+cat issue.rec
+echo
+for i in $(_list $1) ; do
+    echo name: $i
+    cat $i/meta
+    echo
+done
diff --git a/cd b/cd
index 0f945871b4ce0db063739ef7913b8d3c161955bac8f940451f0eeba4a93a0e8b..6e2cc818d8444f2754295109dd73ff2e0307b17f9d2dc9faceb81f5880c4d047 100755 (executable)
--- a/cd
+++ b/cd
@@ -1,14 +1,8 @@
-#!/usr/bin/env zsh
+#!/bin/sh -e
 
-root=$0:h:a
-. $root/lib.zsh.rc
-
-opts=(
-    --bind=ctrl-e:preview-page-down
-    --bind=ctrl-y:preview-page-up
-    --ansi
-    --tac
-    --preview="$root/show {1}"
-)
-$root/list $@ | fzf $opts | cut -f2 | read d
-print issues/$d
+SROOT="$(dirname "$(realpath -- "$0")")"
+. $SROOT/rc.sh
+_list . | fzf --ansi --tac \
+    --bind=ctrl-e:preview-page-down \
+    --bind=ctrl-y:preview-page-up \
+    --preview="$SROOT/full {1} | recsel"
diff --git a/comment b/comment
deleted file mode 100755 (executable)
index 09025ea..0000000
--- a/comment
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-usage() {
-    die Usage: $0 ISSUE
-}
-
-[[ -n $1 ]] || usage
-issue=${1#issues/}
-comment=issues/$issue/comment
-
-local tmp=`mktemp`
-trap "rm -f $tmp" HUP PIPE INT QUIT TERM EXIT
-{ $PERL -npe 's/^/# /' <$comment ; print } >$tmp
-zmodload -F zsh/stat b:zstat
-zstat -A ctimePrev +ctime $tmp
-$EDITOR $tmp
-zstat -A ctime +ctime $tmp
-[[ $ctime != $ctimePrev ]] || {
-    print Aborting comment >&2
-    exit 0
-}
-if [[ -s $comment ]]; then
-    $PERL -ne 'print if $can; if (/^$/) { $can=1 };' <$tmp >$comment
-else
-    cat <$tmp >$comment
-fi
-git add $comment
diff --git a/comment-list b/comment-list
deleted file mode 100755 (executable)
index 1565bb8..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-[[ -n $NO_COLOR ]] || coloured_git=--color
-
-issue=${1#issues/}
-[[ -e issues/$issue/comment ]] || exit 0
-hashes=(`git log --format=format:%H issues/$issue/comment`)
-for i ({${#hashes}..1}) {
-    comment=`git cat-file blob ${hashes[$i]}:issues/$issue/comment | sed "s/^/+ /"`
-    [[ -n $comment ]] || continue
-    header=`git show $coloured_git --no-patch --format=format:"%CgreenWhen: %ai%Creset%n%CredAuthor: %an <%ae>%Creset%n%CblueComment:%Creset" ${hashes[$i]}`
-    print $header
-    print $comment
-    [[ $i -eq 1 ]] || print
-}
diff --git a/dep-add b/dep-add
deleted file mode 100755 (executable)
index 4e3c7a0..0000000
--- a/dep-add
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-usage() {
-    die Usage: $0 ISSUE-DST ISSUE-SRC
-}
-
-[[ -n $1 ]] || usage
-[[ -n $2 ]] || usage
-dst=issues/${1#issues/}
-src=issues/${2#issues/}
-[[ -d $dst ]] || die Unexistent dst
-[[ -d $src ]] || die Unexistent src
-dst=$dst:a
-src=$src:a
-mkdir -p $dst/deps
-cd $dst/deps
-ln -f -s `relative $src:h`/$src:t
-git add $src:t
diff --git a/full b/full
new file mode 100755 (executable)
index 0000000..46fa219
--- /dev/null
+++ b/full
@@ -0,0 +1,15 @@
+#!/bin/sh -e
+
+SROOT="$(dirname "$(realpath -- "$0")")"
+. $SROOT/rc.sh
+
+cat issue.rec
+echo
+for i in $(_list $1) ; do
+    echo name: $i
+    cat $i/meta
+    echo | recins \
+        -f about -v "$(echo ; cat $i/about)" \
+        -f result -v "$(echo ; cat $i/result)"
+    echo
+done
diff --git a/lib.zsh.rc b/lib.zsh.rc
deleted file mode 100644 (file)
index a6357cb..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-setopt ERR_EXIT PIPE_FAIL EXTENDED_GLOB GLOB_STAR_SHORT
-
-PERL=${PERL:-perl}
-EDITOR=${EDITOR:-vi}
-
-die() {
-    print $@ >&2
-    exit 1
-}
-
-[[ -d issues ]] || {
-    cd ..
-    [[ -d issues ]] || die You must run that command in directory with issues/
-}
-
-autoload -Uz relative
-
-endash() {
-    $PERL -npe 's/(-+)/$1-/g ; s/ /-/g'
-}
-
-dedash() {
-    $PERL -npe 's/([^-])-([^-])/$1 $2/g ; s/-(-+)/$1/g'
-}
-
-split-issue() {
-    local cols=(${(s/-/)1})
-    REPLY=("${cols[1]}-${cols[2]}-${cols[3]:s/T/ /}" ${(j/-/)cols[4,-1]})
-}
-
-# Usage: colourise ColourSpec:RegExp [ColourSpec:RegExp ...]
-# It is intended to be a drop-in replacement for supercat utility.
-# ColourSpec is comma delimited list of ANSI codes for colour setting.
-# Look for zsh'es Functions/Misc/colors documentation about possible
-# colour values. For example to set bold red on green background you use
-# "bold,fg-red,bg-green" as a ColourSpec.
-# RegExp is POSIX extended regular expression of the text you want to
-# colourise.
-colourise() {
-    if [[ -n $NO_COLOR ]] || [[ $# -eq 0 ]] ; then
-        cat
-        return
-    fi
-    (( ${+colour} )) || { autoload -Uz colors ; colors ; }
-    local lc=$'\e[' rc=m colr=""
-    local spec=(${(s/:/)1})
-    shift
-    for c (${(s/,/)${spec[1]}}) colr="${col}${lc}${colour[$c]}${rc}"
-    local re=${(j/:/)spec[2,-1]}
-    sed -E "s/${re}/${colr}&${reset_color}/g" | colourise $@
-}
-
-delim() {
-    {
-        local i
-        for i ({1..40}) print -n -- -
-        print
-    } | colourise magenta:".*"
-}
diff --git a/list b/list
deleted file mode 100755 (executable)
index 2eb7a11..0000000
--- a/list
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env zsh
-# Usage: list [[!]tag-expr ...]
-# List all issues. If tag-expr is specified, then include issues only
-# with at least one tag satisfying tag-expr. tag-expr is an expression
-# tag is compared with. If "!" is prepended to tag-expr, then it excludes
-# issues.
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-tagsOur=($@)
-
-tagsSatisfied() {
-    local tagsTheir=($@)
-    for our ($tagsOur) {
-        [[ ${our[1]} = "!" ]] || continue
-        for their ($tagsTheir) {
-            if [[ $their =~ ${our[2,-1]} ]]; then
-                return 1
-            fi
-        }
-    }
-    for our ($tagsOur) {
-        [[ ${our[1]} != "!" ]] || continue
-        local satisfied=0
-        for their ($tagsTheir) {
-            if [[ $their =~ $our ]]; then
-                satisfied=1
-                break
-            fi
-        }
-        [[ $satisfied -eq 1 ]] || return 1
-    }
-}
-
-for issue (issues/*(On)) {
-    issue=$issue:t
-    issue=${issue#issues/}
-
-    tagsTheir=(`$root/tag-list $issue`)
-    tagsSatisfied $tagsTheir || continue
-
-    print -n $issue "| "
-    print $tagsTheir
-} |
-colourise green:"^.{19}" bg-cyan:"\|" red:"proj:[^ ]+"
diff --git a/rc.sh b/rc.sh
new file mode 100644 (file)
index 0000000..bb97bc2
--- /dev/null
+++ b/rc.sh
@@ -0,0 +1,12 @@
+endash() {
+    ${PERL:-perl} -npe 's/(-+)/$1-/g ; s/ /-/g'
+}
+
+_list() {
+    what=${1:-.}
+    for i in $(find $what -type d) ; do
+        i=${i#./}
+        [ -s $i/meta ] || continue
+        echo $i
+    done
+}
diff --git a/recfile-export b/recfile-export
deleted file mode 100755 (executable)
index b4aed74..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-issue=${1#issues/}
-split-issue $issue
-created=${REPLY[1]}
-name=${REPLY[2]}
-print Issue: $issue
-print Created: $created
-print Name: `print $name | dedash`
-for tag (`$root/tag-list $issue`) print Tag: $tag
-for dep (issues/$issue/deps/*(N)) {
-    _dep=$(relative $(realpath $dep))
-    print Depends: ${_dep#issues/}
-}
-print About:
-sed "s/^/+ /" <issues/$issue/about
-print Result:
-sed "s/^/+ /" <issues/$issue/result
-print Comments:
-NO_COLOR=1 $root/comment-list $issue | sed "s/^/+ /"
diff --git a/recfile-export-all b/recfile-export-all
deleted file mode 100755 (executable)
index 1e79dac..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-for issue (issues/*(On)) {
-    $root/recfile-export $issue
-    print
-}
diff --git a/show b/show
deleted file mode 100755 (executable)
index 59a483a..0000000
--- a/show
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-issue=${1#issues/}
-split-issue $issue
-created=${REPLY[1]}
-name=${REPLY[2]}
-print $created \| `print $name | dedash`
-$root/tag-list $issue | colourise cyan:".*"
-deps=(issues/$issue/deps/*(N))
-[[ ${#deps} -eq 0 ]] || {
-    delim
-    print Depends on:
-    for dep ($deps) {
-        _dep=$(relative $(realpath $dep))
-        print "\t"${_dep#issues/}
-    }
-}
-delim
-cat issues/$issue/about
-[[ ! -s issues/$issue/result ]] || {
-    delim
-    cat issues/$issue/result
-}
-delim
-$root/comment-list $issue
diff --git a/tag-add b/tag-add
deleted file mode 100755 (executable)
index e26df32..0000000
--- a/tag-add
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-usage() {
-    die Usage: $0 ISSUE TAG "[TAG ...]"
-}
-
-[[ -n $1 ]] || usage
-issue=${1#issues/}
-[[ -d issues/$issue ]] || die Unknown issue
-shift
-for tag ($@) {
-    tag=tags/$tag:t
-    tag=$tag:a
-    [[ -r $tag ]] || die Unknown tag
-    mkdir -p issues/$issue/tags
-    ln -f -s \
-        `cd issues/$issue/tags ; relative $tag:h`/$tag:t \
-        issues/$issue/tags/$tag:t
-    git add issues/$issue/tags/$tag:t
-}
diff --git a/tag-list b/tag-list
deleted file mode 100755 (executable)
index 011225d..0000000
--- a/tag-list
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env zsh
-
-root=$0:h:a
-. $root/lib.zsh.rc
-
-issue=${1#issues/}
-tags=()
-for t (issues/$issue/tags/*(NOn)) tags=($t:t $tags)
-[[ ${#tags} -eq 0 ]] || print $tags
diff --git a/uuidv7 b/uuidv7
new file mode 100755 (executable)
index 0000000..83ec83c
--- /dev/null
+++ b/uuidv7
@@ -0,0 +1,29 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+my $len = 10;
+my $v;
+open(my $rnd, "/dev/urandom") or die "open(urandom): $!";
+die "read(urandom) != $len" if (read($rnd, $v, $len) != $len);
+close $rnd;
+my @a = unpack "C*", ("000000" . $v);
+
+use Time::HiRes qw(gettimeofday);
+my ($sec, $ms) = gettimeofday();
+my $ts = int($sec * 1000 + $ms / 1000);
+
+$a[0] = ($ts >> 40) & 0xFF;
+$a[1] = ($ts >> 32) & 0xFF;
+$a[2] = ($ts >> 24) & 0xFF;
+$a[3] = ($ts >> 16) & 0xFF;
+$a[4] = ($ts >> 8) & 0xFF;
+$a[5] = $ts & 0xFF;
+
+$a[6] = ($a[6] | 0x0F) | 0x70;
+$a[8] = ($a[8] | 0x3F) | 0x80;
+
+print join("-", map { unpack("H*", pack("C*", @$_)) } (
+    [@a[0..3]], [@a[4..5]], [@a[6..7]], [@a[8..9]], [@a[10..15]],
+)) . "\n";