about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xshatom258
1 files changed, 134 insertions, 124 deletions
diff --git a/shatom b/shatom index 48b99ff..3f9adac 100755 --- a/shatom +++ b/shatom
@@ -1,97 +1,105 @@
1#!/bin/sh 1#!/bin/sh
2# generate a Atom feed from a directory, recursively 2# SHATOM: generate a feed from a directory of files
3# using POSIX sh 3# Copyright (C) 2020--2022 Case Duckworth <acdw@acdw.net>
4# AUTHOR: Case Duckworth <acdw@acdw.net> 4# Licensed under the Good Choices License. See COPYING for details.
5# LICENSE: MIT
6 5
7usage() { 6### Entry point
8 cat <<END
9$0: generate an Atom feed from directories of files
10INVOCATION:
11 $0 [-h] [-c CONFIG] DIRECTORY...
12
13OPTIONS:
14 -h show this help
15 -c CONFIG change the CONFIG file.
16 Default: $PWD/$0.conf.sh
17END
18}
19
20# FEED VARIABLES
21FEED_TITLE=example
22FEED_SUBTITLE="an example"
23FEED_URL="https://example.com/atom.xml"
24SITE_URL="https://example.com"
25FEED_ID="${SITE_URL#*//}"
26FEED_AUTHOR="nobody"
27FEED_COPYRIGHT="(c) 2020 CC-BY-SA $FEED_AUTHOR"
28FEED_UPDATED=$(date -u +'%FT%TZ')
29
30# FUNCTIONS
31
32recent_files() { # recent_files DIRECTORY FIND_ARG...
33 # list files, in order of recency
34 # BOO: REQUIRES GNU FIND :( :( :( :(
35 # possibly look into https://unix.stackexchange.com/questions/9247/
36 # for more possibilities if you don't have GNU find.
37 dir="$1"
38 shift
39
40 find "$dir" -maxdepth 1 \
41 "$@" \
42 -not -name '.*' \
43 -printf '%T@\t%p\n' |
44 sort -nr |
45 cut -f2
46}
47 7
48# ENTRY FUNCTIONS 8usage() {
9 cat <<EOF
10SHATOM: generate a web feed from directories, recursively
11USAGE: shatom -h
12 shatom [-c CONFIG] [-C DIR] [VAR=VALUE...] DIRECTORY...
49 13
50skip_entry() { 14FLAGS:
51 return 1 15 -h Show this help and exit.
52}
53 16
54entry_url() { 17OPTIONS:
55 basename="${1#$DIRECTORY}" 18 -c CONFIG Specify shatom's configuration path, a shell
56 echo "$FEED_URL/$basename" 19 script overriding functions in shatom itself.
20 Default: \$PWD/shatom.conf.sh
21 -C DIR Change the current working directory to DIR
22 before executing the rest of the script.
23
24PARAMETERS:
25 VAR=VALUE... Override configuration variables for this
26 invocation of shatom. These are eval'd, so
27 be careful!
28 DIRECTORY... Directory(s) containing feed content. These
29 can be files as well, but really directories
30 makes more sense, don't you think?
31EOF
32 exit "${1:-0}"
57} 33}
58 34
59entry_title() { 35main() {
60 awk '/^#+[ ]\S/{ 36 # Command-line flags
61 for(i=2;i<=NF;i++) { 37 while getopts hc: opt; do
62 printf $i; 38 case "$opt" in
63 if (i!=NF) printf " "; 39 h) usage ;;
64 } 40 c) CONFIG="$OPTARG" ;;
65 printf "\n";exit}' "$1" 41 *) usage 1 ;;
42 esac
43 done
44 shift $((OPTIND - 1))
45
46 # Configuration file
47 if [ -f "${CONFIG:=$PWD/shatom.conf.sh}" ]; then
48 . "$CONFIG"
49 fi
50
51 # Command-line variable assignment
52 for arg; do
53 case "$arg" in
54 *=*) eval "$arg" ;;
55 *) break ;;
56 esac
57 done
58
59 # Finally, default values for variables
60 FEED_TITLE="${FEED_TITLE:-example}"
61 FEED_SUBTITLE="${FEED_SUBTITLE:-an example}"
62 FEED_AUTHOR="${FEED_AUTHOR:-nobody}"
63 FEED_COPYRIGHT="${FEED_COPYRIGHT:-Copyright (C) $(date +%Y) $FEED_AUTHOR}"
64 FEED_URL="${FEED_URL:-https://example.com/feed.xml}"
65 FEED_ID="${FEED_ID:-${FEED_URL#*//}}"
66 FEED_UPDATED="${FEED_UPDATED:-$(date -u +'%F %TZ')}"
67 SITE_URL="${SITE_URL:-https://example.com/}"
68
69 # Do the damn thing already
70 feed_header
71 for DIR; do
72 recent_files "$DIR" -type f |
73 while read -r item; do
74 skip_item "$item" && continue
75 feed_item "$item"
76 done
77 done
78 feed_footer
66} 79}
67 80
68entry_summary() { 81### Library functions
69 return 1
70}
71 82
72entry_author() { 83## File-listing functions
73 echo "$FEED_AUTHOR"
74}
75 84
76entry_content() { 85recent_files() {
77 cat "$1" 86 # requires stat(1)
78} 87 dir="$1"
88 shift
79 89
80entry_updated() { 90 find "$dir"/* -prune "$@" \
81 # requires stat(1). 91 -exec stat -c '%Y %N' '{}' + |
82 # probably another way. 92 sort -k1,1 -nr |
83 # possibly using ls(1). 93 cut -d' ' -f2-
84 stat -c '%y' "$1" |
85 awk '{
86 sub(/\..*/,"",$2);
87 sub(/[0-9][0-9]/,"&:",$3);
88 print $1"T"$2$3;}'
89} 94}
90 95
91# ATOM FUNCTIONS 96## Feed-level functions
97# by default, generate Atom feeds
98# (https://datatracker.ietf.org/doc/html/rfc4287), but an RSS configuration file
99# is included in rss.conf.sh.
92 100
93atom_header() { 101feed_header() {
94 cat <<END 102 cat <<END
95<?xml version="1.0" encoding="utf-8"?> 103<?xml version="1.0" encoding="utf-8"?>
96<feed xmlns="http://www.w3.org/2005/Atom"> 104<feed xmlns="http://www.w3.org/2005/Atom">
97<title>$FEED_TITLE</title> 105<title>$FEED_TITLE</title>
@@ -105,60 +113,62 @@ atom_header() {
105END 113END
106} 114}
107 115
108atom_footer() { 116feed_footer() {
109 cat <<END 117 cat <<END
110</feed> 118</feed>
111END 119END
112} 120}
113 121
114atom_entry() { # atom_entry FILE 122feed_item() {
115 ENTRY_URL="$(entry_url "$1")" 123 ITEM_URL="$(item_url "$1")"
116 ENTRY_TITLE="$(entry_title "$1")" 124 cat <<END
117 ENTRY_SUMMARY="$(entry_summary "$1")"
118 ENTRY_AUTHOR="$(entry_author "$1")"
119 ENTRY_CONTENT="$(entry_content "$1")"
120 ENTRY_UPDATED="$(entry_updated "$1")"
121 cat <<END
122<entry> 125<entry>
123<id>$ENTRY_URL</id> 126<id>$ITEM_URL</id>
124<link rel="alternate" href="$ENTRY_URL" /> 127<link rel="alternate" href="$ITEM_URL" />
125<title>$ENTRY_TITLE</title> 128<title>$(item_title "$1")</title>
126<summary>$ENTRY_SUMMARY</summary> 129<summary>$(item_summary "$1")</summary>
127<updated>$ENTRY_UPDATED</updated> 130<updated>$(item_updated "$1")</updated>
128<author><name>$ENTRY_AUTHOR</name></author> 131<author>$(item_author)</author>
129<content type="text"><![CDATA[$ENTRY_CONTENT]]></content> 132<content type="text"><![CDATA[$(item_content "$1")]]></content>
130</entry> 133</entry>
131END 134END
132} 135}
133 136
134main() { 137## Item-level functions
135 CONFIGFILE="$PWD/$0.conf.sh" 138# These can all be, and in fact should be, changed in the configuration file.
136 case "$1" in 139
137 -h) 140skip_item() {
138 usage 141 return 1
139 exit 0 142}
140 ;; 143
141 -c) 144item_url() {
142 CONFIGFILE="$2" 145 basename="${1#$DIRECTORY}"
143 shift 2 146 echo "$FEED_URL/$basename"
144 ;; 147}
145 esac 148
146 149item_title() {
147 if [ -f "CONFIGFILE" ]; then 150 basename="${1#$DIRECTORY}"
148 . "$CONFIGFILE" 151 echo "$basename"
149 fi 152}
150 153
151 atom_header 154item_summary() {
152 for DIR; do 155 return 1
153 for entry in $(recent_files "$DIR" -type f); do 156}
154 if skip_entry "$entry"; then continue; fi 157
155 158item_author() {
156 atom_entry "$entry" 159 echo "$FEED_AUTHOR"
157 done 160}
158 done 161
159 atom_footer 162item_content() {
163 cat "$1"
164}
165
166item_updated() {
167 # requires stat(1)
168 stat -c %y "$1"
160} 169}
161 170
162if [ $DEBUG ]; then set -x; fi 171### Execution section
163 172
164main "$@" 173test -n $DEBUG && set -x
174test -n $SOURCE || main "$@"