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