Mercurial > hg-stable
annotate mercurial/hgweb.py @ 135:c0faf50822ea
change template to a generator
add write call to output generator
author | jake@edge2.net |
---|---|
date | Sat, 21 May 2005 20:31:54 -0700 |
parents | 13d609f8d830 |
children | 0e8d60d2bb2b |
rev | line source |
---|---|
131 | 1 #!/usr/bin/env python |
2 # | |
132 | 3 # hgweb.py - 0.2 - 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
131 | 4 # - web interface to a mercurial repository |
5 # | |
6 # This software may be used and distributed according to the terms | |
7 # of the GNU General Public License, incorporated herein by reference. | |
8 | |
9 # useful for debugging | |
10 import cgitb | |
11 cgitb.enable() | |
12 | |
13 import os, cgi, time, re, difflib, sys, zlib | |
14 from mercurial import hg, mdiff | |
15 | |
16 def nl2br(text): | |
17 return re.sub('\n', '<br />', text) | |
18 | |
19 def obfuscate(text): | |
20 l = [] | |
21 for c in text: | |
22 l.append('&#%d;' % ord(c)) | |
23 return ''.join(l) | |
24 | |
25 def httphdr(type): | |
26 print 'Content-type: %s\n' % type | |
27 | |
135 | 28 def write(*things): |
29 for thing in things: | |
30 if hasattr(thing, "__iter__"): | |
31 for part in thing: | |
32 write(part) | |
33 else: | |
34 sys.stdout.write(str(thing)) | |
35 | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
36 class template: |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
37 def __init__(self, tmpl_dir): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
38 self.tmpl_dir = tmpl_dir |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
39 def do_page(self, tmpl_fn, **map): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
40 txt = file(os.path.join(self.tmpl_dir, tmpl_fn)).read() |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
41 while txt: |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
42 m = re.search(r"#([a-zA-Z0-9]+)#", txt) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
43 if m: |
135 | 44 yield txt[:m.start(0)] |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
45 v = map.get(m.group(1), "") |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
46 if callable(v): |
135 | 47 for y in v(**map): yield y |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
48 else: |
135 | 49 yield v |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
50 txt = txt[m.end(0):] |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
51 else: |
135 | 52 yield txt |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
53 txt = '' |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
54 |
131 | 55 class page: |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
56 def __init__(self, tmpl_dir = "", type="text/html", title="Mercurial Web", |
131 | 57 charset="ISO-8859-1"): |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
58 self.tmpl = template(tmpl_dir) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
59 |
131 | 60 print 'Content-type: %s; charset=%s\n' % (type, charset) |
135 | 61 write(self.tmpl.do_page('htmlstart.tmpl', title = title)) |
131 | 62 |
63 def endpage(self): | |
64 print '</BODY>' | |
65 print '</HTML>' | |
66 | |
67 def show_diff(self, a, b, fn): | |
68 a = a.splitlines(1) | |
69 b = b.splitlines(1) | |
70 l = difflib.unified_diff(a, b, fn, fn) | |
71 print '<pre>' | |
72 for line in l: | |
73 line = cgi.escape(line[:-1]) | |
74 if line.startswith('+'): | |
75 print '<span class="plusline">%s</span>' % (line, ) | |
76 elif line.startswith('-'): | |
77 print '<span class="minusline">%s</span>' % (line, ) | |
78 elif line.startswith('@'): | |
79 print '<span class="atline">%s</span>' % (line, ) | |
80 else: | |
81 print line | |
82 print '</pre>' | |
83 | |
84 class errpage(page): | |
85 def __init__(self): | |
86 page.__init__(self, title="Mercurial Web Error Page") | |
87 | |
88 class change_list(page): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
89 def __init__(self, repo, tmpl_dir, reponame, numchanges = 50): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
90 page.__init__(self, tmpl_dir) |
131 | 91 self.repo = repo |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
92 self.numchanges = numchanges |
135 | 93 write(self.tmpl.do_page('changestitle.tmpl', reponame=reponame)) |
131 | 94 |
95 def content(self, hi=None): | |
96 cl = [] | |
97 count = self.repo.changelog.count() | |
98 if not hi: | |
99 hi = count | |
100 elif hi < self.numchanges: | |
101 hi = self.numchanges | |
102 | |
103 start = 0 | |
104 if hi - self.numchanges >= 0: | |
105 start = hi - self.numchanges | |
106 | |
107 nav = "Displaying Revisions: %d-%d" % (start, hi-1) | |
108 if start != 0: | |
109 nav = ('<a href="?cmd=changes;hi=%d">Previous %d</a> ' \ | |
110 % (start, self.numchanges)) + nav | |
111 if hi != count: | |
112 if hi + self.numchanges <= count: | |
113 nav += ' <a href="?cmd=changes;hi=%d">Next %d</a>' \ | |
114 % (hi + self.numchanges, self.numchanges) | |
115 else: | |
116 nav += ' <a href="?cmd=changes">Next %d</a>' % \ | |
117 self.numchanges | |
118 | |
119 print '<center>%s</center>' % nav | |
120 | |
121 for i in xrange(start, hi): | |
122 n = self.repo.changelog.node(i) | |
123 cl.append((n, self.repo.changelog.read(n))) | |
124 cl.reverse() | |
125 | |
126 print '<table summary="" width="100%" align="center">' | |
127 for n, ch in cl: | |
128 print '<tr><td>' | |
129 self.change_table(n, ch) | |
130 print '</td></tr>' | |
131 print '</table>' | |
132 | |
133 print '<center>%s</center>' % nav | |
134 | |
135 def change_table(self, nodeid, changes): | |
136 hn = hg.hex(nodeid) | |
137 i = self.repo.changelog.rev(nodeid) | |
138 (h1, h2) = [ hg.hex(x) for x in self.repo.changelog.parents(nodeid) ] | |
139 datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0]))) | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
140 files = [] |
131 | 141 for f in changes[3]: |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
142 files.append('<a href="?cmd=file;cs=%s;fn=%s">%s</a> ' \ |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
143 % (hn, f, cgi.escape(f))) |
135 | 144 write(self.tmpl.do_page('change_table.tmpl', |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
145 author=obfuscate(changes[1]), |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
146 desc=nl2br(cgi.escape(changes[4])), date=datestr, |
135 | 147 files=' '.join(files), revnum=i, revnode=hn)) |
131 | 148 |
149 class checkin(page): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
150 def __init__(self, repo, tmpl_dir, nodestr): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
151 page.__init__(self, tmpl_dir) |
131 | 152 self.repo = repo |
153 self.node = hg.bin(nodestr) | |
154 self.nodestr = nodestr | |
155 print '<h3>Checkin: %s</h3>' % nodestr | |
156 | |
157 def content(self): | |
158 changes = self.repo.changelog.read(self.node) | |
159 i = self.repo.changelog.rev(self.node) | |
160 parents = self.repo.changelog.parents(self.node) | |
161 (h1, h2) = [ hg.hex(x) for x in parents ] | |
162 (i1, i2) = [ self.repo.changelog.rev(x) for x in parents ] | |
163 datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0]))) | |
164 mf = self.repo.manifest.read(changes[0]) | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
165 files = [] |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
166 for f in changes[3]: |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
167 files.append('<a href="?cmd=file;nd=%s;fn=%s">%s</a> ' \ |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
168 % (hg.hex(mf[f]), f, cgi.escape(f))) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
169 p2link = h2 |
131 | 170 if i2 != -1: |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
171 p2link = '<a href="?cmd=chkin;nd=%s">%s</a>' % (h2, h2) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
172 |
135 | 173 write(self.tmpl.do_page('checkin.tmpl', revnum=i, revnode=self.nodestr, |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
174 p1num=i1, p1node=h1, p2num=i2, p2node=h2, p2link=p2link, |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
175 mfnum=self.repo.manifest.rev(changes[0]), |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
176 mfnode=hg.hex(changes[0]), author=obfuscate(changes[1]), |
134 | 177 desc=nl2br(cgi.escape(changes[4])), date=datestr, |
135 | 178 files=' '.join(files))) |
131 | 179 |
180 (c, a, d) = self.repo.diffrevs(parents[0], self.node) | |
181 change = self.repo.changelog.read(parents[0]) | |
182 mf2 = self.repo.manifest.read(change[0]) | |
183 for f in c: | |
184 self.show_diff(self.repo.file(f).read(mf2[f]), \ | |
185 self.repo.file(f).read(mf[f]), f) | |
186 for f in a: | |
187 self.show_diff('', self.repo.file(f).read(mf[f]), f) | |
188 for f in d: | |
189 self.show_diff(self.repo.file(f).read(mf2[f]), '', f) | |
190 | |
191 class filepage(page): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
192 def __init__(self, repo, tmpl_dir, fn, node=None, cs=None): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
193 page.__init__(self, tmpl_dir) |
131 | 194 self.repo = repo |
195 self.fn = fn | |
196 if cs: | |
197 chng = self.repo.changelog.read(hg.bin(cs)) | |
198 mf = self.repo.manifest.read(chng[0]) | |
199 self.node = mf[self.fn] | |
200 self.nodestr = hg.hex(self.node) | |
201 else: | |
202 self.nodestr = node | |
203 self.node = hg.bin(node) | |
204 print '<div class="filename">%s (%s)</div>' % \ | |
205 (cgi.escape(self.fn), self.nodestr, ) | |
206 print '<a href="?cmd=hist;fn=%s">history</a><br />' % self.fn | |
207 | |
208 def content(self): | |
209 print '<pre>' | |
210 print cgi.escape(self.repo.file(self.fn).read(self.node)) | |
211 print '</pre>' | |
212 | |
213 class mfpage(page): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
214 def __init__(self, repo, tmpl_dir, node): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
215 page.__init__(self, tmpl_dir) |
131 | 216 self.repo = repo |
217 self.nodestr = node | |
218 self.node = hg.bin(node) | |
219 | |
220 def content(self): | |
221 mf = self.repo.manifest.read(self.node) | |
222 fns = mf.keys() | |
223 fns.sort() | |
135 | 224 write(self.tmpl.do_page('mftitle.tmpl', node = self.nodestr)) |
131 | 225 for f in fns: |
135 | 226 write(self.tmpl.do_page('mfentry.tmpl', fn=f, node=hg.hex(mf[f]))) |
131 | 227 |
228 class histpage(page): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
229 def __init__(self, repo, tmpl_dir, fn): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
230 page.__init__(self, tmpl_dir) |
131 | 231 self.repo = repo |
232 self.fn = fn | |
233 | |
234 def content(self): | |
235 print '<div class="filehist">File History: %s</div>' % self.fn | |
236 r = self.repo.file(self.fn) | |
237 print '<br />' | |
238 print '<table summary="" width="100%" align="center">' | |
239 for i in xrange(r.count()-1, -1, -1): | |
240 print '<tr><td>' | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
241 self.hist_ent(i, r) |
131 | 242 print '</tr></td>' |
243 print '</table>' | |
244 | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
245 def hist_ent(self, i, r): |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
246 n = r.node(i) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
247 (p1, p2) = r.parents(n) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
248 (h, h1, h2) = map(hg.hex, (n, p1, p2)) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
249 (i1, i2) = map(r.rev, (p1, p2)) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
250 ci = r.linkrev(n) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
251 cn = self.repo.changelog.node(ci) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
252 cs = hg.hex(cn) |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
253 changes = self.repo.changelog.read(cn) |
131 | 254 datestr = time.asctime(time.gmtime(float(changes[2].split(' ')[0]))) |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
255 p2entry = '' |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
256 if i2 != -1: |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
257 p2entry = ' %d:<a href="?cmd=file;nd=%s;fn=%s">%s</a>' \ |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
258 % (i2, h2, self.fn, h2 ), |
135 | 259 write(self.tmpl.do_page('hist_ent.tmpl', author=obfuscate(changes[1]), |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
260 csnode=cs, desc=nl2br(cgi.escape(changes[4])), |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
261 date = datestr, fn=self.fn, revnode=h, p1num = i1, |
135 | 262 p1node=h1, p2entry=p2entry)) |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
263 |
132 | 264 class hgweb: |
265 repo_path = "." | |
266 numchanges = 50 | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
267 tmpl_dir = "templates" |
131 | 268 |
132 | 269 def __init__(self): |
270 pass | |
131 | 271 |
132 | 272 def run(self): |
273 | |
274 args = cgi.parse() | |
275 | |
276 ui = hg.ui() | |
277 repo = hg.repository(ui, self.repo_path) | |
131 | 278 |
132 | 279 if not args.has_key('cmd') or args['cmd'][0] == 'changes': |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
280 page = change_list(repo, self.tmpl_dir, 'Mercurial', |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
281 self.numchanges) |
132 | 282 hi = args.get('hi', ( repo.changelog.count(), )) |
283 page.content(hi = int(hi[0])) | |
284 page.endpage() | |
285 | |
286 elif args['cmd'][0] == 'chkin': | |
287 if not args.has_key('nd'): | |
288 page = errpage() | |
289 print '<div class="errmsg">No Node!</div>' | |
290 else: | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
291 page = checkin(repo, self.tmpl_dir, args['nd'][0]) |
132 | 292 page.content() |
293 page.endpage() | |
131 | 294 |
132 | 295 elif args['cmd'][0] == 'file': |
296 if not (args.has_key('nd') and args.has_key('fn')) and \ | |
297 not (args.has_key('cs') and args.has_key('fn')): | |
298 page = errpage() | |
299 print '<div class="errmsg">Invalid Args!</div>' | |
300 else: | |
301 if args.has_key('nd'): | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
302 page = filepage(repo, self.tmpl_dir, |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
303 args['fn'][0], node=args['nd'][0]) |
132 | 304 else: |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
305 page = filepage(repo, self.tmpl_dir, |
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
306 args['fn'][0], cs=args['cs'][0]) |
132 | 307 page.content() |
308 page.endpage() | |
131 | 309 |
132 | 310 elif args['cmd'][0] == 'mf': |
311 if not args.has_key('nd'): | |
312 page = errpage() | |
313 print '<div class="errmsg">No Node!</div>' | |
314 else: | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
315 page = mfpage(repo, self.tmpl_dir, args['nd'][0]) |
132 | 316 page.content() |
317 page.endpage() | |
131 | 318 |
132 | 319 elif args['cmd'][0] == 'hist': |
320 if not args.has_key('fn'): | |
321 page = errpage() | |
322 print '<div class="errmsg">No Filename!</div>' | |
323 else: | |
133
fb84d3e71042
added template support for some hgweb output, also, template files for
jake@edge2.net
parents:
132
diff
changeset
|
324 page = histpage(repo, self.tmpl_dir, args['fn'][0]) |
132 | 325 page.content() |
326 page.endpage() | |
327 | |
328 elif args['cmd'][0] == 'branches': | |
329 httphdr("text/plain") | |
330 nodes = [] | |
331 if args.has_key('nodes'): | |
332 nodes = map(hg.bin, args['nodes'][0].split(" ")) | |
333 for b in repo.branches(nodes): | |
334 print " ".join(map(hg.hex, b)) | |
131 | 335 |
132 | 336 elif args['cmd'][0] == 'between': |
337 httphdr("text/plain") | |
338 nodes = [] | |
339 if args.has_key('pairs'): | |
340 pairs = [ map(hg.bin, p.split("-")) | |
341 for p in args['pairs'][0].split(" ") ] | |
342 for b in repo.between(pairs): | |
343 print " ".join(map(hg.hex, b)) | |
344 | |
345 elif args['cmd'][0] == 'changegroup': | |
346 httphdr("application/hg-changegroup") | |
347 nodes = [] | |
348 if args.has_key('roots'): | |
349 nodes = map(hg.bin, args['roots'][0].split(" ")) | |
131 | 350 |
132 | 351 z = zlib.compressobj() |
352 for chunk in repo.changegroup(nodes): | |
353 sys.stdout.write(z.compress(chunk)) | |
354 | |
355 sys.stdout.write(z.flush()) | |
131 | 356 |
132 | 357 else: |
358 page = errpage() | |
359 print '<div class="errmsg">unknown command: %s</div>' % \ | |
360 cgi.escape(args['cmd'][0]) | |
361 page.endpage() | |
131 | 362 |
132 | 363 if __name__ == "__main__": |
364 hgweb().run() |