]> git.phdru.name Git - phdru.name/phdru.name.git/blob - reindex_blog.py
Moved html parsing from phd_pp.py to reindex_blog.py.
[phdru.name/phdru.name.git] / reindex_blog.py
1 #! /usr/bin/env python
2 # -*- coding: koi8-r -*-
3
4 __version__ = "$Revision$"[11:-2]
5 __revision__ = "$Id$"[5:-2]
6 __date__ = "$Date$"[7:-2]
7 __author__ = "Oleg Broytman <phd@phd.pp.ru>"
8 __copyright__ = "Copyright (C) 2006-2010 PhiloSoft Design"
9
10
11 import sys, os
12
13 blog_data_root = sys.argv[1]
14 blog_root = sys.argv[2]
15 blog_filename = os.path.join(blog_data_root, "blog_dict.pickle")
16
17 try:
18    import cPickle as pickle
19 except ImportError:
20    import pickle
21
22 from Cheetah.Template import Template
23
24
25 # Load old blog
26
27 try:
28    blog_file = open(blog_filename, "rb")
29 except IOError:
30    old_blog = {}
31 else:
32    old_blog = pickle.load(blog_file)
33    blog_file.close()
34
35
36 # blog is a dictionary mapping
37 # (year, month, day) => [list of (file, title, lead, tags)]
38
39 blog = {}
40 years = {}
41
42 # bodies is a dictionary mapping file => body
43
44 bodies = {}
45
46 # Walk the directory recursively
47 for dirpath, dirs, files in os.walk(blog_root):
48    d = os.path.basename(dirpath)
49    if not d.startswith("20") and not d.isdigit():
50       continue
51    for file in files:
52       if not file.endswith(".tmpl"):
53          continue
54       fullpath = os.path.join(dirpath, file)
55       template = Template(file=fullpath)
56       title_parts = template.Title.split()
57       title = ' '.join(title_parts[6:])
58       lead = template.Lead
59
60       tags = template.Tag
61       if isinstance(tags, basestring):
62          tags = (tags,)
63
64       if title:
65          key = year, month, day = tuple(dirpath[len(blog_root):].split(os.sep)[1:])
66          if key in blog:
67             days = blog[key]
68          else:
69             days = blog[key] = []
70          days.append((file, title, lead, tags))
71
72          if year in years:
73             months = years[year]
74          else:
75             months = years[year] = {}
76
77          if month in months:
78             days = months[month]
79          else:
80             days = months[month] = []
81
82          if day not in days: days.append(day)
83
84          file = file[:-len("tmpl")] + "html"
85          key = (year, month, day, file)
86          bodies[key] = template.body()
87
88 # Need to save the blog?
89 if blog <> old_blog:
90    blog_file = open(blog_filename, "wb")
91    pickle.dump(blog, blog_file, pickle.HIGHEST_PROTOCOL)
92    blog_file.close()
93
94 # Localized month names
95
96 import locale
97 locale.setlocale(locale.LC_ALL, "ru_RU.KOI8-R")
98 from calendar import _localized_day, _localized_month
99
100 locale.setlocale(locale.LC_TIME, 'C')
101 months_names_en = list(_localized_month('%B'))
102 months_abbrs_en = list(_localized_month('%b'))
103
104 locale.setlocale(locale.LC_TIME, "ru_RU.KOI8-R")
105 #months_names_ru = list(_localized_month('%B'))
106
107 months_names_ru = ['', "января", "февраля", "марта", "апреля", "мая", "июня",
108    "июля", "августа", "сентября", "октября", "ноября", "декабря"
109 ]
110
111 months_names_ru0 = ['', "январь", "февраль", "март", "апрель", "май", "июнь",
112    "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"
113 ]
114
115 from news import write_if_changed
116
117
118 def write_template(level, year, month, day, titles, tags=None):
119    path = [blog_root]
120    if level >= 1:
121       path.append(year)
122    if level >= 2:
123       path.append(month)
124    if level == 3:
125       path.append(day)
126    path.append("index.tmpl")
127    index_name = os.path.join(*path)
128
129    new_text = ["""\
130 ## THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
131 #extends phd_pp_ru
132 #implements respond
133 """]
134
135    if level == 0:
136       new_text.append("""\
137 #attr $Title = "Oleg Broytman's blog"
138 #attr $Description = "Broytman Russian Blog Index Document"
139 #attr $Copyright = %(cyear)s
140 #attr $alternates = (("Новости [Atom 1.0] только заголовки", "application/atom+xml", "atom_10_titles.xml"),
141                      ("Новости [Atom 1.0]", "application/atom+xml", "atom_10.xml"),
142                      ("Новости [Atom 1.0] полные тексты", "application/atom+xml", "atom_10_full.xml"),
143                      ("Новости [RSS 2.0] только заголовки",  "application/rss+xml",  "rss_20_titles.xml"),
144                      ("Новости [RSS 2.0]",  "application/rss+xml",  "rss_20.xml"),
145                      ("Новости [RSS 2.0] полные тексты",  "application/rss+xml",  "rss_20_full.xml"),
146 )
147 ##
148 #def body_html
149 <h1>Журнал</h1>
150 """ % {"cyear": year or 2005})
151
152    elif level == 1:
153       new_text.append("""\
154 #attr $Title = "Oleg Broytman's blog: %(year)s"
155 #attr $Description = "Broytman Russian Blog %(year)s Index Document"
156 #attr $Copyright = %(cyear)s
157 ##
158 #def body_html
159 <h1>Журнал: %(year)s</h1>
160 """ % {"year": year, "cyear": year or 2005})
161
162    elif level == 2:
163       imonth = int(month)
164       new_text.append("""\
165 #attr $Title = "Oleg Broytman's blog: %(month_abbr_en)s %(year)s"
166 #attr $Description = "Broytman Russian Blog %(month_name_en)s %(year)s Index Document"
167 #attr $Copyright = %(cyear)s
168 ##
169 #def body_html
170 <h1>Журнал: %(month_name_ru0)s %(year)s</h1>
171 """ % {
172       "year": year, "cyear": year or 2005,
173       "month_abbr_en": months_abbrs_en[imonth], "month_name_en": months_names_en[imonth],
174       "month_name_ru0": months_names_ru0[imonth],
175    })
176
177    elif level == 3:
178       iday = int(day)
179       imonth = int(month)
180
181       if len(titles) == 1:
182          new_text.append("""\
183 #attr $refresh = "0; URL=%s"
184 """ % titles[0][3])
185
186       new_text.append("""\
187 #attr $Title = "Oleg Broytman's blog: %(day)d %(month_abbr_en)s %(year)s"
188 #attr $Description = "Broytman Russian Blog %(day)d %(month_name_en)s %(year)s Index Document"
189 #attr $Copyright = %(cyear)s
190 ##
191 #def body_html
192 <h1>Журнал: %(day)d %(month_name_ru)s %(year)s</h1>
193 """ % {
194       "year": year, "cyear": year or 2005,
195       "month_abbr_en": months_abbrs_en[imonth], "month_name_en": months_names_en[imonth],
196       "month_name_ru": months_names_ru[imonth],
197       "day": iday
198    })
199
200    save_titles = titles[:]
201    titles.reverse()
202
203    save_date = None
204    for year, month, day, file, title, lead in titles:
205       href = []
206       if level == 0:
207          href.append(year)
208       if level <= 1:
209          href.append(month)
210       if level <= 2:
211          href.append(day)
212       href.append(file)
213       href = '/'.join(href)
214       if day[0] == '0': day = day[1:]
215       if save_date <> (year, month, day):
216          if level == 0:
217             new_text.append('\n<h2>%s %s %s</h2>' % (day, months_names_ru[int(month)], year))
218          else:
219             new_text.append('\n<h2>%s %s</h2>' % (day, months_names_ru[int(month)]))
220          save_date = year, month, day
221       new_text.append('''
222 <p class="head">
223    %s<a href="%s">%s</a>.
224 </p>
225 ''' % (lead+' ' if lead else '', href, title))
226
227    if level == 0:
228       new_text.append("""
229 <hr>
230
231 <p class="head">Новостевая лента в форматах
232 <img src="../../Graphics/atom_10.jpg" border=0>
233 <A HREF="atom_10_titles.xml">Atom 1.0 только заголовки</A> /
234 <A HREF="atom_10.xml">Atom 1.0</A> /
235 <A HREF="atom_10_full.xml">Atom 1.0 полные тексты</A>
236 и <img src="../../Graphics/rss_20.jpg" border=0>
237 <A HREF="rss_20_titles.xml">RSS 2.0 только заголовки</A> /
238 <A HREF="rss_20.xml">RSS 2.0</A> /
239 <A HREF="rss_20_full.xml">RSS 2.0 полные тексты</A>.
240 </p>
241 """)
242
243       years = {}
244       for year, month, day, file, title, lead in save_titles:
245          years[year] = True
246       new_text.append('''
247 <p class="head"><a href="tags/">Теги</a>:
248 ''')
249       first_tag = True
250       for count, tag, links in all_tags:
251          if first_tag:
252             first_tag = False
253          else:
254             new_text.append(' - ')
255          new_text.append("""<a href="tags/%s.html">%s (%d)</a>""" % (tag, tag, count))
256       new_text.append('''
257 </p>
258 ''')
259
260       max_year = int(sorted(years.keys())[-1])
261       years = range(2005, max_year+1)
262
263       new_text.append('''
264 <p class="head">По годам:
265 ''')
266       first_year = True
267       for year in years:
268          if first_year:
269             first_year = False
270          else:
271             new_text.append(' - ')
272          new_text.append('<a href="%s/">%s</a>' % (year, year))
273       new_text.append('''
274 </p>
275 ''')
276
277       new_text.append("""
278 <hr>
279 <p class="head"><a href="http://phd.livejournal.com/">ЖЖ</a>
280 """)
281
282    new_text.append("""\
283 #end def
284 $phd_pp_ru.respond(self)
285 """)
286
287    write_if_changed(index_name, ''.join(new_text))
288
289
290 all_tags = {}
291 all_titles = []
292 all_titles_tags = []
293
294 for year in sorted(years.keys()):
295    year_titles = []
296    months = years[year]
297    for month in sorted(months.keys()):
298       month_titles = []
299       for day in sorted(months[month]):
300          day_titles = []
301          key = year, month, day
302          if key in blog:
303             for file, title, lead, tags in blog[key]:
304                if file.endswith(".tmpl"): file = file[:-len("tmpl")] + "html"
305                value = (year, month, day, file, title, lead)
306                all_titles_tags.append((year, month, day, file, title, lead, tags))
307                all_titles.append(value)
308                year_titles.append(value)
309                month_titles.append(value)
310                day_titles.append(value)
311                for tag in tags:
312                   if tag in all_tags:
313                      tag_links = all_tags[tag]
314                   else:
315                      tag_links = all_tags[tag] = []
316                   tag_links.append(value)
317          write_template(3, year, month, day, day_titles)
318       write_template(2, year, month, day, month_titles)
319    write_template(1, year, month, day, year_titles)
320
321 def by_count_rev_tag_link(t1, t2):
322    """Sort all_tags by count in descending order,
323    and by tags and links in ascending order
324    """
325    r = cmp(t1[0], t2[0])
326    if r:
327       return -r
328    return cmp((t1[1], t1[2]), (t2[1], t2[2]))
329
330 all_tags = [(len(links), tag, links) for (tag, links) in all_tags.items()]
331 all_tags.sort(by_count_rev_tag_link)
332
333 write_template(0, year, month, day, all_titles[-20:], all_tags)
334
335 new_text = ["""\
336 ## THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
337 #extends phd_pp_ru
338 #implements respond
339 #attr $Title = "Oleg Broytman's blog: tags"
340 #attr $Description = "Broytman Russian Blog Tags Index Document"
341 #attr $Copyright = 2006
342 ##
343 #def body_html
344 <h1>Теги</h1>
345
346 <p class="head">
347 <dl>
348 """]
349
350 for i, (count, tag, links) in enumerate(all_tags):
351    new_text.append("""\
352    <dt><a href="%s.html">%s (%d)</a></dt>
353 """ % (tag, tag, count))
354
355    first = all_tags[0][1]
356    if i == 0:
357       prev = None
358    else:
359       prev = all_tags[i-1][1]
360    if i >= len(all_tags)-1:
361       next = None
362    else:
363       next = all_tags[i+1][1]
364    last = all_tags[-1][1]
365
366    tag_text = ["""\
367 ## THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
368 #extends phd_pp_ru
369 #implements respond
370 #attr $Title = "Oleg Broytman's blog: tag %s"
371 #attr $Description = "Broytman Russian Blog Tag %s Index Document"
372 """ % (tag, tag)]
373
374    tag_text.append("""\
375 #attr $First = "%s"
376 """ % first)
377
378    if prev:
379       tag_text.append("""\
380 #attr $Prev = "%s"
381 """ % prev)
382
383    if next:
384       tag_text.append("""\
385 #attr $Next = "%s"
386 """ % next)
387
388    tag_text.append("""\
389 #attr $Last = "%s"
390 """ % last)
391
392    tag_text.append("""\
393 #attr $Copyright = 2006
394 ##
395 #def body_html
396 <h1>%s</h1>
397
398 <p class="head">
399 <ul>
400 """ % tag)
401
402    count = 0
403    for year, month, day, filename, title, lead in reversed(links):
404       link = "../%s/%s/%s/%s" % (year, month, day, filename)
405       item_text = """<li><a href="%s">%s/%s/%s: %s%s</a></li>""" % (link, year, month, day, lead+' ' if lead else '', title)
406
407       count += 1
408       if count <= 5:
409          new_text.append("      <dd>%s</dd>\n" % item_text)
410
411       tag_text.append("   %s\n" % item_text)
412
413    tag_text.append("""\
414 </ul>
415 </p>
416 #end def
417 $phd_pp_ru.respond(self)
418 """)
419    write_if_changed(os.path.join(blog_root, "tags", tag+".tmpl"), ''.join(tag_text))
420
421 new_text.append("""\
422 </dl>
423 </p>
424 #end def
425 $phd_pp_ru.respond(self)
426 """)
427 write_if_changed(os.path.join(blog_root, "tags", "index.tmpl"), ''.join(new_text))
428
429
430 from HTMLParser import HTMLParseError
431 import cgi
432 from urlparse import urljoin
433 from m_lib.net.www.html import HTMLParser as _HTMLParser
434
435 class HTMLDone(Exception): pass
436
437
438 class FirstPHTMLParser(_HTMLParser):
439    def __init__(self):
440       _HTMLParser.__init__(self)
441       self.first_p = None
442
443    def start_p(self, attrs):
444       self.accumulator = '<p>'
445
446    def end_p(self):
447       self.first_p = self.accumulator + '</p>'
448       raise HTMLDone()
449
450 def get_first_p(body):
451    parser = FirstPHTMLParser()
452
453    try:
454       parser.feed(body)
455    except (HTMLParseError, HTMLDone):
456       pass
457
458    try:
459       parser.close()
460    except (HTMLParseError, HTMLDone):
461       pass
462
463    return parser.first_p
464
465
466 class AbsURLHTMLParser(_HTMLParser):
467    def __init__(self, base):
468       _HTMLParser.__init__(self)
469       self.base = base
470
471    def start_a(self, attrs):
472       self.accumulator += '<a'
473       for attrname, value in attrs:
474          value = cgi.escape(value, True)
475          if attrname == 'href':
476             self.accumulator += ' href="%s"' % urljoin(self.base, value)
477          else:
478             self.accumulator += ' %s="%s"' % (attrname, value)
479       self.accumulator += '>'
480
481    def end_a(self):
482       self.accumulator += '</a>'
483
484 def absolute_urls(body, base):
485    parser = AbsURLHTMLParser(base)
486
487    try:
488       parser.feed(body)
489    except HTMLParseError:
490       pass
491
492    try:
493       parser.close()
494    except HTMLParseError:
495       pass
496
497    return parser.accumulator
498
499
500 from atom_10 import atom_10
501 from rss_20 import rss_20
502 from news import NewsItem
503
504 if blog_root:
505    baseURL = "http://phd.pp.ru/%s/" % blog_root
506 else:
507    baseURL = "http://phd.pp.ru/"
508
509 items = []
510 for item in tuple(reversed(all_titles_tags))[:10]:
511    year, month, day, file, title, lead, tags = item
512    url_path = "%s/%s/%s/%s" % (year, month, day, file)
513    item = NewsItem(
514       "%s-%s-%s" % (year, month, day),
515       "%s%s" % (lead+' ' if lead else '', title),
516       url_path)
517    items.append(item)
518    item.baseURL = baseURL
519    item.categoryList = tags
520    body = bodies[(year, month, day, file)]
521    body = absolute_urls(body, baseURL + url_path)
522    item.body = body
523    item.excerpt = get_first_p(body)
524
525 namespace = {
526    "title": "Oleg Broytman's blog",
527    "baseURL": baseURL,
528    "indexFile": "",
529    "description": "",
530    "lang": "ru",
531    "author": "Oleg Broytman",
532    "email": "phd@phd.pp.ru",
533    "generator": os.path.basename(sys.argv[0]),
534    "posts": items,
535 }
536
537 # For english dates
538 locale.setlocale(locale.LC_TIME, 'C')
539
540 atom_tmpl = str(atom_10(searchList=[namespace]))
541 write_if_changed(os.path.join(blog_root, "atom_10.xml"), atom_tmpl)
542 rss_tmpl = str(rss_20(searchList=[namespace]))
543 write_if_changed(os.path.join(blog_root, "rss_20.xml"), rss_tmpl)
544
545 for item in items:
546     item.excerpt = None
547
548 atom_tmpl = str(atom_10(searchList=[namespace]))
549 write_if_changed(os.path.join(blog_root, "atom_10_titles.xml"), atom_tmpl)
550 rss_tmpl = str(rss_20(searchList=[namespace]))
551 write_if_changed(os.path.join(blog_root, "rss_20_titles.xml"), rss_tmpl)
552
553 for item in items:
554     item.content = item.body
555
556 atom_tmpl = str(atom_10(searchList=[namespace]))
557 write_if_changed(os.path.join(blog_root, "atom_10_full.xml"), atom_tmpl)
558 rss_tmpl = str(rss_20(searchList=[namespace]))
559 write_if_changed(os.path.join(blog_root, "rss_20_full.xml"), rss_tmpl)