Recently Written · git

sbm

dmenu bookmarks. Instantly fuzzy search thousands of bookmarks and plumb them. LOOKING FOR SUCKLESS SOFTWARE EDITION? GO TO sbm-suckless INSTEAD

git clone https://github.com/equwal/sbm

Log | Files | Refs


bm-page (7652 bytes)

1 #!/bin/sh
2 
3 # bm-page: print a web page as plain text: its title, a blank line, then its
4 # text. When it is installed, the fzf list of bm shows the page of the
5 # bookmark under the cursor in the preview, live. SBM_FETCH=0 keeps it off
6 # the network.
7 #
8 #     bm-page https://suckless.org/ | less
9 #
10 # Only http and https pages are fetched. curl stops after 5 seconds, and dd
11 # passes at most 512k of the page. Scripts, styles and comments are left
12 # out. Control characters are removed, so that a page cannot send escape
13 # sequences to the terminal.
14 
15 if [ $# -ne 1 ]; then
16     printf 'usage: bm-page <url>\n' >&2
17     exit 2
18 fi
19 [ "${SBM_FETCH:-1}" != 0 ] || exit 0
20 case $(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') in
21     http://*|https://*) ;;
22     *) exit 0 ;;
23 esac
24 
25 # The first awk reads the page as one record (RS is a control character),
26 # then splits it at each "<". Each part starts with a tag and ends with the
27 # text after it, so one pass through the parts reads the page in linear
28 # time. The second awk tidies the white space.
29 curl -sL --max-time 5 -A 'Mozilla/5.0' -- "$1" 2>/dev/null \
30     | dd bs=1024 count=512 2>/dev/null | LC_ALL=C awk '
31     function utf8(c) {
32         if (c == 160) return " "
33         if (c < 128) return sprintf("%c", c)
34         if (c < 2048) return sprintf("%c%c", 192 + int(c / 64), 128 + c % 64)
35         if (c < 65536)
36             return sprintf("%c%c%c", 224 + int(c / 4096), 128 + int(c / 64) % 64, 128 + c % 64)
37         if (c < 1114112)
38             return sprintf("%c%c%c%c", 240 + int(c / 262144), 128 + int(c / 4096) % 64,
39                            128 + int(c / 64) % 64, 128 + c % 64)
40         return ""
41     }
42     function entity(name,    n, i) {
43         if (name ~ /^#[0-9]+$/) return utf8(substr(name, 2) + 0)
44         if (name ~ /^#[xX][0-9A-Fa-f]+$/) {
45             n = 0
46             for (i = 3; i <= length(name); i++)
47                 n = n * 16 + index("0123456789abcdef", tolower(substr(name, i, 1))) - 1
48             return utf8(n)
49         }
50         if (name in named) return named[name]
51         return "&" name ";"
52     }
53     function decode(s,    out, i, j, name) {
54         out = ""
55         while ((i = index(s, "&")) > 0) {
56             out = out substr(s, 1, i - 1)
57             s = substr(s, i + 1)
58             j = index(s, ";")
59             name = substr(s, 1, j - 1)
60             if (j > 1 && j <= 12 && name ~ /^(#[0-9]+|#[xX][0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]*)$/) {
61                 out = out entity(name)
62                 s = substr(s, j + 1)
63             } else {
64                 out = out "&"
65             }
66         }
67         return out s
68     }
69     # text: the text of the page, or of its title. As in a browser, a line
70     # break in the text is a space. The line breaks come from the tags: brk
71     # tells how many are waiting for the next text.
72     function text(s) {
73         if (intitle) {
74             title = title " " s
75             return
76         }
77         s = decode(s)
78         gsub(/[ \t\r\n]+/, " ", s)
79         if (s == "" || s == " ") {
80             if (s == " " && !brk) printf " "
81             return
82         }
83         if (brk) {
84             printf "%s", brk == 2 ? "\n\n" : "\n"
85             brk = 0
86             sub(/^ /, "", s)
87         }
88         printf "%s", s
89     }
90     BEGIN {
91         RS = "\001"
92         named["amp"] = "&"; named["lt"] = "<"; named["gt"] = ">"; named["quot"] = "\""
93         named["apos"] = "\047"; named["nbsp"] = " "
94         named["ndash"] = utf8(8211); named["mdash"] = utf8(8212); named["hellip"] = utf8(8230)
95         named["lsquo"] = utf8(8216); named["rsquo"] = utf8(8217)
96         named["ldquo"] = utf8(8220); named["rdquo"] = utf8(8221)
97         named["laquo"] = utf8(171); named["raquo"] = utf8(187); named["middot"] = utf8(183)
98         named["bull"] = utf8(8226); named["copy"] = utf8(169); named["reg"] = utf8(174)
99         named["trade"] = utf8(8482); named["deg"] = utf8(176); named["times"] = utf8(215)
100         named["euro"] = utf8(8364)
101         # Tags that start a line, and tags that start a paragraph.
102         split("br div li tr dt dd header footer nav main section article aside " \
103               "figure figcaption form details summary address hr", b, " ")
104         for (i in b) line[b[i]] = 1
105         split("p h1 h2 h3 h4 h5 h6 ul ol dl table blockquote pre", b, " ")
106         for (i in b) para[b[i]] = 1
107         # Tags whose content is no text of the page.
108         split("script style noscript template svg iframe object", b, " ")
109         for (i in b) hidden[b[i]] = 1
110     }
111     { page = page $0 }
112     END {
113         if (substr(page, 1, 5) == "%PDF-") {
114             print "(a PDF document)"
115             exit
116         }
117         n = split(page, part, "<")
118         text(part[1])
119         for (i = 2; i <= n; i++) {
120             p = part[i]
121             if (comment || substr(p, 1, 3) == "!--") {
122                 k = index(p, "-->")
123                 comment = !k
124                 if (k && skip == "") text(substr(p, k + 3))
125                 continue
126             }
127             k = index(p, ">")
128             if (!k) {
129                 # A "<" in the text, or the end of a page that dd cut off.
130                 if (skip == "" && i < n) text("<" p)
131                 continue
132             }
133             tag = tolower(substr(p, 1, k - 1))
134             closing = substr(tag, 1, 1) == "/"
135             name = tag
136             sub(/^\//, "", name)
137             sub(/[^a-z0-9].*/, "", name)
138             rest = substr(p, k + 1)
139             if (skip != "") {
140                 if (closing && name == skip) {
141                     skip = ""
142                     text(rest)
143                 }
144                 continue
145             }
146             if (name in hidden && !closing && substr(tag, length(tag)) != "/") {
147                 skip = name
148                 continue
149             }
150             if (name == "title" && !done) {
151                 if (closing) {
152                     intitle = 0
153                     done = 1
154                     t = decode(title)
155                     gsub(/[ \t\r\n]+/, " ", t)
156                     printf "\n\001T%s\n", t
157                     brk = 0
158                 } else {
159                     intitle = 1
160                 }
161             } else if (name == "main") {
162                 # Marks for the second awk: the main part of the page.
163                 printf "\n\001%s\n", closing ? ">" : "<"
164                 brk = 0
165             } else if (name in para) {
166                 brk = 2
167             } else if (name in line) {
168                 if (!brk) brk = 1
169             } else if (name == "td" || name == "th") {
170                 text(" ")
171             }
172             text(rest)
173         }
174         print ""
175     }' | LC_ALL=C awk '
176     {
177         gsub(/\302\240/, " ")
178         gsub(/\302[\200-\237]/, "")
179         gsub(/[ \t\r]+/, " ")
180         sub(/^ /, "")
181         sub(/ $/, "")
182     }
183     /^\001T/ {
184         if (title == "") {
185             title = substr($0, 3)
186             sub(/^ /, "", title)
187         }
188         next
189     }
190     /^\001</ { if (!opened) { opened = 1; from = n + 1 }; next }
191     /^\001>/ { if (opened && !closed) { closed = 1; to = n }; next }
192     { line[++n] = $0 }
193     # The title, then the main part of the page when the page marks one, so
194     # that menus do not fill the preview. At most one blank line in a row.
195     END {
196         if (!opened) from = 1
197         if (!closed) to = n
198         if (title != "") {
199             print title
200             seen = blank = 1
201         }
202         for (i = from; i <= to; i++) {
203             if (line[i] == "") {
204                 blank = seen
205                 continue
206             }
207             if (blank) print ""
208             print line[i]
209             seen = 1
210             blank = 0
211         }
212     }' | tr -d '\000-\010\013-\037\177'