diff options
-rw-r--r-- | fold.sh | 26 | ||||
-rw-r--r-- | test.gmi | 57 | ||||
-rw-r--r-- | transform_uri.sh | 157 | ||||
-rw-r--r-- | typeset_gemini.awk | 122 | ||||
-rw-r--r-- | typeset_gemini.sh | 168 |
5 files changed, 373 insertions, 157 deletions
diff --git a/fold.sh b/fold.sh new file mode 100644 index 0000000..ee88c6e --- /dev/null +++ b/fold.sh | |||
@@ -0,0 +1,26 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | |||
3 | fold() { | ||
4 | shopt -s checkwinsize | ||
5 | ( | ||
6 | : | ||
7 | : | ||
8 | ) | ||
9 | width="${1:-$COLUMNS}" | ||
10 | while read -r line; do | ||
11 | : "${line// / }" | ||
12 | IFS=$'\n' read -d "" -ra words <<<"${line// /$'\n'}" | ||
13 | ll=0 | ||
14 | for word in "${words[@]}"; do | ||
15 | wl="${#word}" | ||
16 | if ((ll + wl > width)); then | ||
17 | printf '\n' | ||
18 | else | ||
19 | ((ll += wl)) | ||
20 | printf '%s ' "$word" | ||
21 | fi | ||
22 | done | ||
23 | done | ||
24 | } | ||
25 | |||
26 | fold "$@" | ||
diff --git a/test.gmi b/test.gmi new file mode 100644 index 0000000..d7cce94 --- /dev/null +++ b/test.gmi | |||
@@ -0,0 +1,57 @@ | |||
1 | Welcome to Conman Laboraties Gemini Server! | ||
2 | The Number 1 Gemini Server | ||
3 | (that is, the first one, not necessarily the best one) | ||
4 | |||
5 | This is your host, Sean. | ||
6 | |||
7 | => http://boston.conman.org/ Blog | ||
8 | => gopher://gopher.conman.org/1phlog.gopher Phlog | ||
9 | => mailto:sean@conman.org Email | ||
10 | => http://www.conman.org/people/spc/ Web | ||
11 | |||
12 | * * * * * | ||
13 | |||
14 | This is the site of the first Gemini servers in existance. Things are pretty rough around here as we work out what exactly is needed and what isn't. If you want to check out the source code that runs this place: | ||
15 | |||
16 | => news.txt News about this server | ||
17 | => /gRFC/ Gemini Request For Comments | ||
18 | => gemini://gemini.conman.org/sourcecode/ Source Code | ||
19 | => https://github.com/spc476/GLV-1.12556 Source Code on Github | ||
20 | => /extensions/ Custom Extensions to the Source Code | ||
21 | => /sourcecode/ Alternative Link | ||
22 | => /modules/ Modules | ||
23 | => /source-code/ Old link | ||
24 | |||
25 | And some other content you might find intersting: | ||
26 | |||
27 | => /hilo/ A Simple Guessing Game | ||
28 | => /test/torture/ Gemini Client Torture Test | ||
29 | => /test/testwrap.gemini Text wrapping samples | ||
30 | => /cgi/test We support CGI scripts! | ||
31 | => /scgi-sample We support SCGI programs! | ||
32 | => /king_james_bible.gemini The King James Bible | ||
33 | => /qotd Quote O' the Moment | ||
34 | => gemini://zaibatsu.circumlunar.space/spec-spec.txt Specification | ||
35 | => /test/ Test files | ||
36 | => /obsolete Outdated Docs | ||
37 | => /no-longer-here/ Old stuff | ||
38 | |||
39 | A Private Area is available for your perusal---all that's needed is a client certificate to be presented. Any client certificate will do. Just create one (there are many guides online for how to do it), and ensure it's available when making a request to this area. | ||
40 | |||
41 | => /private/ Private Area | ||
42 | |||
43 | There's also the Conman Labs Private Area. This is off limits to non-authorized personel. Please contact sean@conman.org about getting access to this restricted area. | ||
44 | |||
45 | => /conman-labs-private/ Conman Labs Private Area | ||
46 | |||
47 | Other Gemini Servers---These were the first five to be created: | ||
48 | |||
49 | => gemini://gemini.conman.org/ The First One | ||
50 | => gemini://zaibatsu.circumlunar.space/ Project Gemini | ||
51 | => gemini://carcosa.net/ Carcosa.Net | ||
52 | => gemini://heavysquare.com/ HeavySquare | ||
53 | => gemini://mozz.us/ Mozz | ||
54 | |||
55 | => gemini://gus.guru/known-hosts And an up-to-date list of servers | ||
56 | |||
57 | Thank you for your support. | ||
diff --git a/transform_uri.sh b/transform_uri.sh deleted file mode 100644 index e9c9fc9..0000000 --- a/transform_uri.sh +++ /dev/null | |||
@@ -1,157 +0,0 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | # transform-url | ||
3 | # cf. https://tools.ietf.org/html/rfc3986#section-5 and | ||
4 | # cf. https://tools.ietf.org/html/rfc3986#section-5.1 | ||
5 | # cf. also https://tools.ietf.org/html/rfc3986#appendix-B -- regex | ||
6 | |||
7 | # TEST WITH https://tools.ietf.org/html/rfc3986#section-5.4 | ||
8 | |||
9 | transform_resource() { # 5.2.2 | ||
10 | declare -A R B T # reference, base url, target | ||
11 | eval "$(parse_url R "$2")" # XXX CHANGE | ||
12 | eval "$(parse_url B "$1")" | ||
13 | # Basically going to follow the pseudocode in the spec. | ||
14 | # the '+x' bit after the fields of the arrays tests if they're set | ||
15 | if [[ "${R['scheme']+x}" ]]; then | ||
16 | T['scheme']="${R['scheme']}" | ||
17 | T['authority']="${R['authority']}" | ||
18 | T['path']="$(remove_dot_segments "${R['path']}")" | ||
19 | T['query']="${R['query']}" | ||
20 | else | ||
21 | if [[ "${R['authority']+x}" ]]; then | ||
22 | T['authority']="${R['authority']}" | ||
23 | T['path']="$(remove_dot_segments "${R['path']}")" | ||
24 | T['query']="${R['query']}" | ||
25 | else | ||
26 | if [[ "${R['path']-x}" == "" ]]; then | ||
27 | T['path']="${B['path']}" | ||
28 | if [[ "${R['query']-x}" ]]; then | ||
29 | T['query']="${R['query']}" | ||
30 | else | ||
31 | T['query']="${B['query']}" | ||
32 | fi | ||
33 | else | ||
34 | if [[ "${R['path']}" == /* ]]; then | ||
35 | T['path']="$(remove_dot_segments "${R['path']}")" | ||
36 | else | ||
37 | T['path']="$(merge "${B['authority']-?}" \ | ||
38 | "${B['path']}" "${R['path']}")" | ||
39 | T['path']="$(remove_dot_segments "${T['path']}")" | ||
40 | fi | ||
41 | T['query']="${R['query']}" | ||
42 | fi | ||
43 | T['authority']="${B['authority']}" | ||
44 | fi | ||
45 | T['scheme']="${B['scheme']}" | ||
46 | fi | ||
47 | T['fragment']="${R['fragment']}" | ||
48 | # 5.3 -- recomposition | ||
49 | local r="" | ||
50 | [[ "${T['scheme']-x}" ]] && | ||
51 | r="$r${T['scheme']}:" | ||
52 | [[ "${T['authority']-x}" ]] && | ||
53 | r="$r//${T['authority']}" | ||
54 | r="$r${T['path']}" | ||
55 | [[ "${T['query']-x}" ]] && | ||
56 | r="$r?${T['query']}" | ||
57 | [[ "${T['fragment']-x}" ]] && | ||
58 | r="$r#${T['fragment']}" | ||
59 | printf '%s\n' "$r" | ||
60 | } | ||
61 | |||
62 | merge() { # 5.2.3 | ||
63 | #>If the base URI has a defined authority component and an empty | ||
64 | #>path, then return a string consisting of "/" concatenated with the | ||
65 | #>reference's path; otherwise, | ||
66 | #>return a string consisting of the reference's path component | ||
67 | #>appended to all but the last segment of the base URI's path (i.e., | ||
68 | #>excluding any characters after the right-most "/" in the base URI | ||
69 | #>path, or excluding the entire base URI path if it does not contain | ||
70 | #>any "/" characters). | ||
71 | B_authority="$1" # if ? is here, it means undefined (see caller) | ||
72 | B_path="$2" | ||
73 | R_path="$3" | ||
74 | if [[ -z "$R_path" ]]; then | ||
75 | printf '%q\n' "$B_path" | | ||
76 | sed 's,//,/,g' # XXX is this okay....? | ||
77 | return | ||
78 | fi | ||
79 | |||
80 | if [[ "${B_authority:-?}" != "?" && "${B_path-x}" == "" ]]; then | ||
81 | printf '/%q\n' "$R_path" | ||
82 | else | ||
83 | if [[ "$B_path" == */* ]]; then | ||
84 | B_path="${B_path%/*}/" | ||
85 | else | ||
86 | B_path="" | ||
87 | fi | ||
88 | printf '%q/%q\n' "$B_path" "$R_path" # XXX - %q vs %s | ||
89 | fi | ||
90 | } | ||
91 | |||
92 | # I can probably just use normalize_path already in bollux here | ||
93 | remove_dot_segments() { # 5.2.4 | ||
94 | local input="$1" | ||
95 | local output= | ||
96 | while [[ -n "$input" ]]; do | ||
97 | if [[ "$input" == ../* || "$input" == ./* ]]; then | ||
98 | input="${input#*/}" | ||
99 | elif [[ "$input" == /./* ]]; then | ||
100 | input="${input#/./}/" | ||
101 | elif [[ "$input" == /.* ]]; then | ||
102 | input="${input#/.}/b" | ||
103 | elif [[ "$input" == /../* ]]; then | ||
104 | input="${input#/../}/c" | ||
105 | output="${output%/*}" | ||
106 | elif [[ "$input" == /..* ]]; then | ||
107 | input="${input#/..}/d" | ||
108 | output="${output%/*}" | ||
109 | elif [[ "$input" == . || "$input" == .. ]]; then | ||
110 | input= | ||
111 | else | ||
112 | # move the first path segment in the input buffer to the end of | ||
113 | # the output buffer, including the initial "/" character (if | ||
114 | # any) and any subsequent characters up to, but not including, | ||
115 | # the next "/" character or the end of the input buffer. | ||
116 | [[ $input =~ ^(/?[^/]*)(/?.*)$ ]] || echo NOMATCH >&2 | ||
117 | output="$output${BASH_REMATCH[1]}" | ||
118 | input="${BASH_REMATCH[2]}" | ||
119 | fi | ||
120 | done | ||
121 | printf '%s\n' "$output" | | ||
122 | sed 's,//,/,g' # XXX is this okay....? | ||
123 | } | ||
124 | |||
125 | # *FINDING* URLS ... IN PURE BASH !!! | ||
126 | parse_url() { # eval "$(split_url NAME STRING)" => NAME[...] | ||
127 | local name="$1" | ||
128 | local string="$2" | ||
129 | local re='^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?' | ||
130 | [[ $string =~ $re ]] || return $? | ||
131 | |||
132 | local scheme="${BASH_REMATCH[2]}" | ||
133 | local authority="${BASH_REMATCH[4]}" | ||
134 | local path="${BASH_REMATCH[5]}" | ||
135 | local query="${BASH_REMATCH[7]}" | ||
136 | local fragment="${BASH_REMATCH[9]}" | ||
137 | |||
138 | for c in scheme authority path query fragment; do | ||
139 | [[ "${!c}" ]] && | ||
140 | printf '%s[%s]=%s\n' "$name" "$c" "${!c}" | | ||
141 | sed 's/[\|&;()<>]/\\&/g' # quote shell metacharacters | ||
142 | done | ||
143 | } | ||
144 | |||
145 | # ease-of-life functions | ||
146 | isdefined() { # isdefined NAME => tests if NAME is defined ONLY | ||
147 | [[ "${!1+x}" ]] | ||
148 | } | ||
149 | isempty() { # isempty NAME => tests if NAME is empty ONLY | ||
150 | [[ ! "${!1-x}" ]] | ||
151 | } | ||
152 | |||
153 | set -x | ||
154 | transform_resource "$@" | ||
155 | |||
156 | # NEXT .... | ||
157 | # NORMALIZATION !!! | ||
diff --git a/typeset_gemini.awk b/typeset_gemini.awk new file mode 100644 index 0000000..eb5b7e1 --- /dev/null +++ b/typeset_gemini.awk | |||
@@ -0,0 +1,122 @@ | |||
1 | BEGIN { | ||
2 | pre = 0 | ||
3 | margin = margin ? margin : 4 | ||
4 | # Core lines | ||
5 | txs = "" # text style | ||
6 | lns = "\033[1m" # link number style | ||
7 | lus = "\033[36m" # link url style | ||
8 | lts = "\033[4m" # link text style | ||
9 | pfs = "" # preformatted style | ||
10 | # Advanced lines | ||
11 | h1s = "\033[1;4m" # h1 style | ||
12 | h2s = "\033[1m" # h2 style | ||
13 | h3s = "\033[3m" # h3 style | ||
14 | lis = "" # list item style | ||
15 | # Reset | ||
16 | res = "\033[0m" # reset style | ||
17 | } | ||
18 | /```/ { | ||
19 | pre = ! pre | ||
20 | next | ||
21 | } | ||
22 | pre { | ||
23 | mark = "```" | ||
24 | fmt = pfs "%s" res | ||
25 | text = $0 | ||
26 | } | ||
27 | /^#/ { | ||
28 | match($0, /#+/) | ||
29 | mark = substr($0, RSTART, RLENGTH) | ||
30 | sub(/#+[[:space:]]*/, "", $0) | ||
31 | level = length(mark) | ||
32 | if (level == 1) { | ||
33 | fmt = h1s "%s" res | ||
34 | } else if (level == 2) { | ||
35 | fmt = h2s "%s" res | ||
36 | } else { | ||
37 | fmt = h3s "%s" res | ||
38 | } | ||
39 | } | ||
40 | /^=>/ { | ||
41 | mark = "=>" | ||
42 | sub(/=>[[:space:]]*/, "", $0) | ||
43 | desc = $1 | ||
44 | text = "" | ||
45 | for (w = 2; w <= NF; w++) { | ||
46 | text = text (text ? " " : "") $w | ||
47 | } | ||
48 | fmt = lns "[" (++ln) "]" res " " lts "%s" res "\t" lus "%s" res | ||
49 | } | ||
50 | /^\*[[:space:]]/ { | ||
51 | mark = "*" | ||
52 | sub(/\*[[:space:]]*/, "", $0) | ||
53 | fmt = lis "%s" res | ||
54 | } | ||
55 | { | ||
56 | mark = mark ? mark : mark | ||
57 | fmt = fmt ? fmt : "%s" | ||
58 | text = text ? text : fold($0, " ") | ||
59 | desc = desc ? desc : "" | ||
60 | printf "%-" margin "s" fmt "\n", mark, text, desc | ||
61 | mark = fmt = text = desc = "" | ||
62 | } | ||
63 | function fold(str, sep, cols, out, cmd, i, j, len, chars, c, last, f, first) | ||
64 | { | ||
65 | if (! cols) { | ||
66 | # checks if stdout is a tty | ||
67 | if (system("test -t 1")) { | ||
68 | cols = 80 | ||
69 | } else { | ||
70 | cmd = "tput cols" | ||
71 | cmd | getline cols | ||
72 | close(cmd) | ||
73 | } | ||
74 | } | ||
75 | # squeeze tabs and newlines to spaces | ||
76 | gsub(/[\t\n]/, " ", str) | ||
77 | # if "sep" is empty, just fold on cols with substr | ||
78 | if (! length(sep)) { | ||
79 | len = length(str) | ||
80 | out = substr(str, 1, cols) | ||
81 | for (i = cols + 1; i <= len; i += cols) { | ||
82 | out = out "\n" | ||
83 | for (j = 1; j < margin; j++) { | ||
84 | out = out " " | ||
85 | } | ||
86 | out = out substr(str, i, cols) | ||
87 | } | ||
88 | return out | ||
89 | # otherwise, we have to loop over every character (can't split() on sep, it | ||
90 | # would destroy the existing separators) | ||
91 | } else { | ||
92 | # split string into char array | ||
93 | len = split(str, chars, "") | ||
94 | # set boolean, used to assign the first line differently | ||
95 | first = 1 | ||
96 | for (i = 1; i <= len; i += last) { | ||
97 | f = 0 | ||
98 | for (c = i + cols - 1; c >= i; c--) { | ||
99 | if (index(sep, chars[c])) { | ||
100 | last = c - i + 1 | ||
101 | f = 1 | ||
102 | break | ||
103 | } | ||
104 | } | ||
105 | if (! f) { | ||
106 | last = cols | ||
107 | } | ||
108 | if (first) { | ||
109 | out = substr(str, i, last) | ||
110 | first = 0 | ||
111 | } else { | ||
112 | out = out "\n" | ||
113 | for (j = 0; j < margin; j++) { | ||
114 | out = out " " | ||
115 | } | ||
116 | out = out substr(str, i, cols) | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | # return the output | ||
121 | return out | ||
122 | } | ||
diff --git a/typeset_gemini.sh b/typeset_gemini.sh new file mode 100644 index 0000000..e8aed41 --- /dev/null +++ b/typeset_gemini.sh | |||
@@ -0,0 +1,168 @@ | |||
1 | typeset_gemini() { | ||
2 | local pre=false | ||
3 | |||
4 | while read -r line; do | ||
5 | case "$line" in | ||
6 | '```') | ||
7 | flip pre | ||
8 | continue | ||
9 | ;; | ||
10 | =\>*) gemini_link "$line" $pre ;; | ||
11 | \#*) gemini_header "$line" $pre ;; | ||
12 | \**) gemini_list "$line" $pre ;; | ||
13 | *) gemini_text "$line" $pre ;; | ||
14 | esac | ||
15 | done | ||
16 | } | ||
17 | |||
18 | gemini_link() { | ||
19 | local re="^(=>)[[:blank:]]*([^[:blank:]]+)[[:blank:]]*(.*)" | ||
20 | local s t a # sigil, text, annotation(url) | ||
21 | if ! ${2-false} && [[ "$1" =~ $re ]]; then | ||
22 | s="${BASH_REMATCH[1]}" | ||
23 | t="${BASH_REMATCH[3]}" | ||
24 | a="${BASH_REMATCH[2]}" | ||
25 | |||
26 | printf "$C_SIGIL%-${MARGIN}s" "$s" | ||
27 | fold_line "$WIDTH" "$(printf "$C_LINK_TITLE%s $C_LINK_URL%s$C_RESET\n" \ | ||
28 | "$t" "$a")" | ||
29 | else | ||
30 | gemini_pre "$1" | ||
31 | fi | ||
32 | } | ||
33 | |||
34 | gemini_header() { | ||
35 | local re="^(#+)[[:blank:]]*(.*)" | ||
36 | local s t a # sigil, text, annotation(lvl) | ||
37 | if ! ${2-false} && [[ "$1" =~ $re ]]; then | ||
38 | s="${BASH_REMATCH[1]}" | ||
39 | a="${#BASH_REMATCH[1]}" | ||
40 | t="${BASH_REMATCH[2]}" | ||
41 | |||
42 | local hdrfmt | ||
43 | hdrfmt="$(eval echo "\$C_HEADER$a")" | ||
44 | printf "$C_SIGIL%-${MARGIN}s$hdrfmt%s$C_RESET\n" \ | ||
45 | "$s" "$(fold_line "$WIDTH" "$t")" | ||
46 | else | ||
47 | gemini_pre "$1" | ||
48 | fi | ||
49 | } | ||
50 | |||
51 | gemini_list() { | ||
52 | local re="^(\*)[[:blank:]]*(.*)" | ||
53 | local s t a # sigil, text, annotation(n/a) | ||
54 | if ! ${2-false} && [[ "$1" =~ $re ]]; then | ||
55 | s="${BASH_REMATCH[1]}" | ||
56 | t="${BASH_REMATCH[2]}" | ||
57 | |||
58 | printf "$C_SIGIL%-${MARGIN}s$C_LIST%s$C_RESET\n" \ | ||
59 | "$s" "$(fold_line "$WIDTH" "$t")" | ||
60 | else | ||
61 | gemini_pre "$1" | ||
62 | fi | ||
63 | } | ||
64 | |||
65 | gemini_text() { | ||
66 | printf "%${MARGIN}s" ' ' | ||
67 | if ! ${2-false}; then | ||
68 | fold_line "$WIDTH" "$1" | ||
69 | else | ||
70 | gemini_pre "$1" | ||
71 | fi | ||
72 | } | ||
73 | |||
74 | gemini_pre() { | ||
75 | printf "%${MARGIN}s%s" ' ' "$1" | ||
76 | } | ||
77 | |||
78 | flip() { # flip NAME | ||
79 | [[ "${!1}" == true || "${!1}" == false ]] || return 1 | ||
80 | |||
81 | if "${!1}"; then | ||
82 | eval "$1=false" | ||
83 | else | ||
84 | eval "$1=true" | ||
85 | fi | ||
86 | } | ||
87 | |||
88 | fold_line() { # fold_line WIDTH TEXT | ||
89 | local width="$1" | ||
90 | local ll=0 wl plain | ||
91 | # shellcheck disable=2086 | ||
92 | # TODO: determine if this is the best way to do it | ||
93 | set -- $2 | ||
94 | |||
95 | for word; do | ||
96 | plain="${word//$'\x1b'\[*([0-9;])m/}" | ||
97 | wl=$((${#plain} + 1)) | ||
98 | if (((ll + wl) >= width)); then | ||
99 | printf "\n%${MARGIN}s" ' ' | ||
100 | ll=$wl | ||
101 | else | ||
102 | ll=$((ll + wl)) | ||
103 | fi | ||
104 | printf '%s ' "$word" | ||
105 | done | ||
106 | printf '\n' | ||
107 | } | ||
108 | |||
109 | # just here for reference | ||
110 | strip() { # strip control sequences | ||
111 | # https://stackoverflow.com/a/55872518 | ||
112 | shopt -s extglob | ||
113 | while IFS='' read -r x; do | ||
114 | # remove colors | ||
115 | echo "${x//$'\x1b'\[*([0-9;])m/}" | ||
116 | done | ||
117 | } | ||
118 | |||
119 | test() { | ||
120 | MARGIN=4 | ||
121 | WIDTH=60 | ||
122 | #shopt -s checkwinsize; (:;:) | ||
123 | #WIDTH="$((COLUMNS - (MARGIN*2)))" | ||
124 | C_LINK_TITLE=$'\e[34m' | ||
125 | C_LINK_URL=$'\e[31m' | ||
126 | C_RESET=$'\e[0m' | ||
127 | typeset_gemini <<-'EOF' | ||
128 | # Project Gemini | ||
129 | |||
130 | ## Overview | ||
131 | |||
132 | Gemini is a new internet protocol which: | ||
133 | |||
134 | * Is heavier than gopher | ||
135 | * Is lighter than the web | ||
136 | * Will not replace either | ||
137 | * Strives for maximum power to weight ratio | ||
138 | * Takes user privacy very seriously | ||
139 | |||
140 | ## Resources | ||
141 | |||
142 | => docs/ Gemini documentation | ||
143 | => software/ Gemini software | ||
144 | => servers/ Known Gemini servers | ||
145 | => https://lists.orbitalfox.eu/listinfo/gemini Gemini mailing list | ||
146 | => gemini://gemini.conman.org/test/torture/ Gemini client torture test | ||
147 | |||
148 | ## Web proxies | ||
149 | |||
150 | => https://portal.mozz.us/?url=gemini%3A%2F%2Fgemini.circumlunar.space%2F&fmt=fixed Gemini-to-web proxy service | ||
151 | => https://proxy.vulpes.one/gemini/gemini.circumlunar.space Another Gemini-to-web proxy service | ||
152 | |||
153 | ## Search engines | ||
154 | |||
155 | => gemini://gus.guru/ Gemini Universal Search engine | ||
156 | => gemini://houston.coder.town Houston search engine | ||
157 | |||
158 | ## Geminispace aggregators (experimental!) | ||
159 | |||
160 | => capcom/ CAPCOM | ||
161 | => gemini://rawtext.club:1965/~sloum/spacewalk.gmi Spacewalk | ||
162 | |||
163 | ## Free Gemini hosting | ||
164 | |||
165 | => users/ Users with Gemini content on this server | ||
166 | EOF | ||
167 | } | ||
168 | test | ||