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