diff options
-rw-r--r-- | README.gmi | 4 | ||||
-rwxr-xr-x | jimmy | 240 | ||||
-rw-r--r-- | test.gmi | 6 |
3 files changed, 160 insertions, 90 deletions
diff --git a/README.gmi b/README.gmi new file mode 100644 index 0000000..3abdd2a --- /dev/null +++ b/README.gmi | |||
@@ -0,0 +1,4 @@ | |||
1 | # jimmy --- convert .gmi to .html and other formats | ||
2 | |||
3 | [to be documented] | ||
4 | |||
diff --git a/jimmy b/jimmy index ed6c2b7..f5dfa0f 100755 --- a/jimmy +++ b/jimmy | |||
@@ -4,16 +4,22 @@ | |||
4 | # init buffers | 4 | # init buffers |
5 | buff="$(mktemp)" | 5 | buff="$(mktemp)" |
6 | lbuf="$(mktemp)" | 6 | lbuf="$(mktemp)" |
7 | trap 'rm "$buff" "$lbuf"' EXIT INT KILL | 7 | meta="$(mktemp)" |
8 | cleanup() { rm "$buff" "$lbuff" "$meta" 2>/dev/null; } | ||
9 | trap cleanup EXIT INT KILL | ||
8 | 10 | ||
9 | # init state | 11 | # init state |
10 | prev= | 12 | prev= # previous linetype |
11 | curr= | 13 | curr= # current linetype |
12 | verbatim=false | 14 | tmpl= # template (optional) |
15 | verbatimp=false # in verbatim block? | ||
16 | metap=true # in metadata block? | ||
17 | IPATH="$PWD" # inclusion path | ||
13 | # (tuneables) | 18 | # (tuneables) |
14 | nl='::NL::' | 19 | : "${nl:=::NL::}" # newline |
15 | sp='::SP::' | 20 | : "${sp:=::SP::}" # space |
16 | to=html | 21 | : "${te:=::END::}" # template end |
22 | : "${to:=html}" # output format | ||
17 | 23 | ||
18 | ### Formats | 24 | ### Formats |
19 | ## HTML and GMI are given here. Other formats can be defined in their | 25 | ## HTML and GMI are given here. Other formats can be defined in their |
@@ -23,64 +29,62 @@ to=html | |||
23 | # should we allow modifying variables from the environment ? | 29 | # should we allow modifying variables from the environment ? |
24 | 30 | ||
25 | html() { | 31 | html() { |
26 | fmtbuff_hd_1="<h1>%s</h1>$nl" | 32 | : "${fmtbuff_hd_1:=<h1>%s</h1>$nl}" |
27 | fmtline_hd_1="%s" | 33 | : "${fmtline_hd_1:=%s}" |
28 | fmtbuff_hd_2="<h2>%s</h2>$nl" | 34 | : "${fmtbuff_hd_2:=<h2>%s</h2>$nl}" |
29 | fmtline_hd_2="%s" | 35 | : "${fmtline_hd_2:=%s}" |
30 | fmtbuff_hd_3="<h3>%s</h3>$nl" | 36 | : "${fmtbuff_hd_3:=<h3>%s</h3>$nl}" |
31 | fmtline_hd_3="%s" | 37 | : "${fmtline_hd_3:=%s}" |
32 | fmtbuff_quot="<blockquote>$nl%s</blockquote>$nl" | 38 | : "${fmtbuff_quot:=<blockquote>$nl%s</blockquote>$nl}" |
33 | fmtline_quot="%s$nl" | 39 | : "${fmtline_quot:=%s$nl}" |
34 | fmtbuff_list="<ul>$nl%s</ul>$nl" | 40 | : "${fmtbuff_list:=<ul>$nl%s</ul>$nl}" |
35 | fmtline_list="<li>%s</li>$nl" | 41 | : "${fmtline_list:=<li>%s</li>$nl}" |
36 | fmtbuff_para="<p>%s</p>$nl" | 42 | : "${fmtbuff_para:=<p>%s</p>$nl}" |
37 | fmtline_para="%s$nl" | 43 | : "${fmtline_para:=%s$nl}" |
38 | fmtline_plnk="<a href=\"%s\">%s</a>$nl" | 44 | : "${fmtline_plnk:=<a href=\"%s\">%s</a>$nl}" |
39 | fmtbuff_link="<ul class=\"links\">$nl%s</ul>$nl" | 45 | : "${fmtbuff_link:=<ul class=\"links\">$nl%s</ul>$nl}" |
40 | fmtline_link="<li><a href=\"%s\">%s</li>$nl" | 46 | : "${fmtline_link:=<li><a href=\"%s\">%s</li>$nl}" |
41 | fmtbuff_verb="<pre><code>%s</code></pre>$nl" | 47 | : "${fmtbuff_verb:=<pre><code>%s</code></pre>$nl}" |
42 | fmtline_verb="%s$nl" | 48 | : "${fmtline_verb:=%s$nl}" |
43 | fmtbuff_blank="$nl" | 49 | : "${fmtbuff_blank:=$nl}" |
44 | fmtline_blank="$nl" | 50 | : "${fmtline_blank:=$nl}" |
45 | } | 51 | } |
46 | 52 | ||
47 | gmi() { | 53 | gmi() { |
48 | fmtbuff_hd_1="# %s$nl" | 54 | : "${fmtbuff_hd_1:=# %s$nl}" |
49 | fmtline_hd_1="%s" | 55 | : "${fmtline_hd_1:=%s}" |
50 | fmtbuff_hd_2="## %s$nl" | 56 | : "${fmtbuff_hd_2:=## %s$nl}" |
51 | fmtline_hd_2="%s" | 57 | : "${fmtline_hd_2:=%s}" |
52 | fmtbuff_hd_3="### %s$nl" | 58 | : "${fmtbuff_hd_3:=### %s$nl}" |
53 | fmtline_hd_3="%s" | 59 | : "${fmtline_hd_3:=%s}" |
54 | fmtbuff_quot="> %s$nl" | 60 | : "${fmtbuff_quot:=> %s$nl}" |
55 | fmtline_quot="%s$sp" | 61 | : "${fmtline_quot:=%s$sp}" |
56 | fmtbuff_list="%s$nl" | 62 | : "${fmtbuff_list:=%s$nl}" |
57 | fmtline_list="* %s$nl" | 63 | : "${fmtline_list:=* %s$nl}" |
58 | fmtbuff_para="%s$nl" | 64 | : "${fmtbuff_para:=%s$nl}" |
59 | fmtline_para="%s$sp" | 65 | : "${fmtline_para:=%s$sp}" |
60 | fmtline_plnk="$nl=> %s %s$nl" | 66 | : "${fmtline_plnk:=$nl=> %s %s$nl}" |
61 | fmtbuff_link="%s$nl" | 67 | : "${fmtbuff_link:=%s$nl}" |
62 | fmtline_link="=> %s %s" | 68 | : "${fmtline_link:==> %s %s}" |
63 | fmtbuff_verb="\`\`\`$nl%s\`\`\`$nl" | 69 | : "${fmtbuff_verb:=\`\`\`$nl%s\`\`\`$nl}" |
64 | fmtline_verb="%s$nl" | 70 | : "${fmtline_verb:=%s$nl}" |
65 | fmtbuff_blank="$nl" | 71 | : "${fmtbuff_blank:=$nl}" |
66 | fmtline_blank="$nl" | 72 | : "${fmtline_blank:=$nl}" |
67 | } | 73 | } |
68 | 74 | ||
69 | ### Filters | 75 | ### Filters |
70 | 76 | ||
71 | filter_buff_html(){ cat; } | ||
72 | |||
73 | filter_buff() { | 77 | filter_buff() { |
74 | f="filter_buff_$to" | 78 | f="filter_buff_$to" |
75 | if type "$f" | grep -q function | 79 | if type "$f" 2>/dev/null | grep -q function |
76 | then "$f" | 80 | then "$f" |
77 | else cat | 81 | else cat |
78 | fi | sed -e "s/$nl/\n/g" -e "s/$sp/ /g" # fix whitespace | 82 | fi |
79 | } | 83 | } |
80 | 84 | ||
81 | filter_line() { | 85 | filter_line() { |
82 | f="filter_line_$to" | 86 | f="filter_line_$to" |
83 | if type "$f" | grep -q function | 87 | if type "$f" 2>/dev/null | grep -q function |
84 | then printf '%s\n' "$*" | "$f" | 88 | then printf '%s\n' "$*" | "$f" |
85 | else printf '%s\n' "$*" | 89 | else printf '%s\n' "$*" |
86 | fi | 90 | fi |
@@ -89,12 +93,16 @@ filter_line() { | |||
89 | filter_line_html() { | 93 | filter_line_html() { |
90 | # s/// : escape <, >, & from html | 94 | # s/// : escape <, >, & from html |
91 | # s### : *bold*, _italic_, `code` | 95 | # s### : *bold*, _italic_, `code` |
92 | sed -e 's/&/\&/g' \ | 96 | # s@@@ : smart versions of things |
93 | -e 's/</\</g' \ | 97 | sed \ |
94 | -e 's/>/\>/g' \ | 98 | -e 's/&/\&/g' \ |
95 | -e 's#\*\([^*]*\)\*#<b>\1</b>#g' \ | 99 | -e 's/</\</g' \ |
96 | -e 's#_\([^_]*\)_#<i>\1</i>#g' \ | 100 | -e 's/>/\>/g' \ |
97 | -e 's#`\([^`]*\)`#<code>\1</code>#' | 101 | -e 's#\*\([^*]*\)\*#<b>\1</b>#g' \ |
102 | -e 's#_\([^_]*\)_#<i>\1</i>#g' \ | ||
103 | -e 's#`\([^`]*\)`#<code>\1</code>#' \ | ||
104 | -e 's@---@\—@g' \ | ||
105 | -e 's@--@\–@g' | ||
98 | 106 | ||
99 | } | 107 | } |
100 | 108 | ||
@@ -108,46 +116,55 @@ pushline() { | |||
108 | } | 116 | } |
109 | 117 | ||
110 | bufprint() { | 118 | bufprint() { |
111 | b="$(cat<"$buff")" | 119 | b="$(filter_buff<"$buff")" |
112 | printf "$(eval echo "\$fmtbuff_$1")" "$b" | filter_buff | 120 | printf "$(eval echo "\$fmtbuff_$1")" "$b" | |
121 | sed -e "s/$nl/\n/g" -e "s/$sp/ /g" # fix whitespace | ||
113 | : > "$buff" | 122 | : > "$buff" |
114 | } | 123 | } |
115 | 124 | ||
116 | ## Where the magic happens | 125 | ### Where the magic happens |
117 | process() { | 126 | process() { |
118 | set -f | 127 | set -f |
119 | while read -r sigil line | 128 | while read -r sigil line |
120 | do | 129 | do |
121 | if $verbatim && test "$sigil" != '```' | 130 | if $verbatimp && test "$sigil" != '```' |
122 | then | 131 | then |
123 | pushline verb "$(filter_line "$sigil $line")" | 132 | pushline verb "$(filter_line "$sigil $line")" |
124 | continue | 133 | continue |
125 | fi | 134 | fi |
126 | 135 | ||
127 | case "$sigil" in | 136 | case "$sigil" in |
128 | ('```') | 137 | (*':') # metadata |
129 | if $verbatim | 138 | if $metap |
139 | then printf 'export %s="%s"\n' \ | ||
140 | "${sigil%:}" "$line" >>"$meta" | ||
141 | fi | ||
142 | ;; | ||
143 | ('```') # verbatim | ||
144 | # CONSIDER: "types" of verbatim | ||
145 | # designated by extra fields after the | ||
146 | # sigil | ||
147 | ## ``` class_of_content | ||
148 | # ^--- change the class of the content, | ||
149 | # eg. in html do <pre class="type"> | ||
150 | # other formats might do other things | ||
151 | ## ``` | some_program | ||
152 | # ^--- pipe the buffer to some_program | ||
153 | metap=false | ||
154 | if $verbatimp | ||
130 | then | 155 | then |
131 | bufprint verb | 156 | bufprint verb |
132 | verbatim=false | 157 | verbatimp=false |
133 | prev= | 158 | prev= |
134 | else | 159 | else |
135 | # CONSIDER: "types" of verbatim | ||
136 | # designated by extra fields after the | ||
137 | # sigil | ||
138 | ## ``` class_of_content | ||
139 | # ^- change the class of the content, | ||
140 | # eg. in html do <pre class="type"> | ||
141 | # other formats might do other things | ||
142 | ## ``` | some_program | ||
143 | # ^- pipe the buffer to some_program | ||
144 | ## others? | ||
145 | bufprint "$prev" | 160 | bufprint "$prev" |
146 | verbatim=true | 161 | verbatimp=true |
147 | fi | 162 | fi |
148 | continue | 163 | continue |
149 | ;; | 164 | ;; |
150 | ('=>') | 165 | ('=>') # link |
166 | metap=false | ||
167 | |||
151 | printf '%s\n' "$line" > "$lbuf" | 168 | printf '%s\n' "$line" > "$lbuf" |
152 | read -r url title < "$lbuf" | 169 | read -r url title < "$lbuf" |
153 | if test "$curr" = para | 170 | if test "$curr" = para |
@@ -157,11 +174,20 @@ process() { | |||
157 | else curr=link | 174 | else curr=link |
158 | fi | 175 | fi |
159 | ;; | 176 | ;; |
160 | ('#'*) curr=hd_${#sigil} ;; | 177 | ('#'*) # header |
161 | ('>') curr=quot ;; | 178 | metap=false |
162 | ('*') curr=list ;; | 179 | curr=hd_${#sigil} ;; |
163 | ('') curr=blank ;; | 180 | ('>') # quote |
164 | (*) | 181 | metap=false |
182 | curr=quot ;; | ||
183 | ('*') # list | ||
184 | metap=false | ||
185 | curr=list ;; | ||
186 | ('') # blank line | ||
187 | metap=false | ||
188 | curr=blank ;; | ||
189 | (*) # paragraph | ||
190 | metap=false | ||
165 | curr=para | 191 | curr=para |
166 | line="$sigil $line" | 192 | line="$sigil $line" |
167 | ;; | 193 | ;; |
@@ -185,28 +211,43 @@ process() { | |||
185 | bufprint "$curr" | 211 | bufprint "$curr" |
186 | } | 212 | } |
187 | 213 | ||
214 | templatize() { | ||
215 | eval "cat<<$te | ||
216 | $(cat $@) | ||
217 | $te" | ||
218 | } | ||
219 | |||
188 | ### Entry point | 220 | ### Entry point |
189 | 221 | ||
190 | usage() { | 222 | usage() { |
191 | cat <<EOF >&2 | 223 | cat <<EOF >&2 |
192 | jimmy: convert gmi to other formats | 224 | jimmy: convert gmi to other formats |
193 | usage: jimmy [-h] [-t FORMAT] [FILE...] | 225 | usage: jimmy [-h] [-t FORMAT] [-I DIRECTORY] [-T FILE] [FILE...] |
194 | If no FILE is given on the command line, jimmy reads standard input. | 226 | If no FILE is given on the command line, jimmy reads standard input. |
195 | options: | 227 | options: |
196 | -h show this help and exit | 228 | -h show this help and exit |
229 | -x enable xtrace (set -x) | ||
197 | -t FORMAT | 230 | -t FORMAT |
198 | convert gmi to FORMAT. html is default, gmi is built-in. | 231 | convert gmi to FORMAT. html is default, gmi is built-in. |
199 | you can also pass the name of a file that will be sourced. | 232 | you can also pass the name of a file that will be sourced. |
233 | -I DIRECTORY | ||
234 | add DIRECTORY to the include path for -t. the current | ||
235 | directory is always in the include path. | ||
236 | -T FILE | ||
237 | use FILE as a template for the output text. | ||
200 | EOF | 238 | EOF |
239 | exit $1 | ||
201 | } | 240 | } |
202 | 241 | ||
203 | main() { | 242 | main() { |
204 | while getopts ht:x OPT | 243 | while getopts hxI:t:T: OPT |
205 | do | 244 | do |
206 | case "$OPT" in | 245 | case "$OPT" in |
207 | (h) usage 0 ;; | 246 | (h) usage 0 ;; |
208 | (t) to="$OPTARG" ;; | ||
209 | (x) set -x ;; | 247 | (x) set -x ;; |
248 | (I) IPATH="$OPTARG:$IPATH" ;; | ||
249 | (t) to="$OPTARG" ;; | ||
250 | (T) tmpl="$OPTARG" ;; | ||
210 | (*) usage 1 ;; | 251 | (*) usage 1 ;; |
211 | esac | 252 | esac |
212 | done | 253 | done |
@@ -214,15 +255,36 @@ main() { | |||
214 | 255 | ||
215 | case "$to" in | 256 | case "$to" in |
216 | (html|gmi) "$to" ;; | 257 | (html|gmi) "$to" ;; |
217 | (*) . "$to" || { | 258 | (*) |
218 | echo >&2 "Can't find file: '$to'" | 259 | found=false |
219 | exit 2 | 260 | for p in $(echo "$IPATH"|tr : ' ') |
220 | } | 261 | do |
221 | ;; | 262 | if test -f "$p/$to" |
263 | then . "$p/$to"; found=true | ||
264 | elif test -f "$p/$to.sh" | ||
265 | then . "$p/$to.sh"; found=true | ||
266 | fi | ||
267 | done | ||
268 | if ! $found | ||
269 | then | ||
270 | echo >&2 "Can't find file: '$to'" | ||
271 | echo >&2 "Looked in $IPATH" | ||
272 | exit 2 | ||
273 | fi | ||
274 | ;; | ||
222 | esac | 275 | esac |
223 | 276 | ||
224 | # while read requires a final newline | 277 | # while read requires a final newline |
225 | (cat "${@:--}"; echo) | process | 278 | (cat "${@:--}"; echo) | |
279 | process | | ||
280 | if test -n "$tmpl" | ||
281 | then | ||
282 | # use eval cat instead of source for pipe sequencing | ||
283 | # reasons | ||
284 | eval "$(cat "$meta")" | ||
285 | templatize "$tmpl" | ||
286 | else cat | ||
287 | fi | ||
226 | } | 288 | } |
227 | 289 | ||
228 | main "$@" | 290 | main "$@" |
diff --git a/test.gmi b/test.gmi index f2dc0dc..62bed2e 100644 --- a/test.gmi +++ b/test.gmi | |||
@@ -1,3 +1,7 @@ | |||
1 | title: a test document | ||
2 | date: 2024-05-13T03:02:45Z | ||
3 | uuid: b3daebf1-440b-4828-a4d9-9089c7bd7c61 | ||
4 | |||
1 | # a test document of some kind | 5 | # a test document of some kind |
2 | 6 | ||
3 | here is a test document. | 7 | here is a test document. |
@@ -30,4 +34,4 @@ as well as `code`, _emph_ and such? | |||
30 | what if *i _nest_ them* | 34 | what if *i _nest_ them* |
31 | what if *i _nest them* wrong_ ? | 35 | what if *i _nest them* wrong_ ? |
32 | what about *breaking them | 36 | what about *breaking them |
33 | over two lines?* \ No newline at end of file | 37 | over two lines?* |