blogit.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8
  3. # Copyleft (C) 2010 Mir Nazim <hello@mirnazim.org>
  4. #
  5. # Everyone is permitted to copy and distribute verbatim or modified
  6. # copies of this license document, and changing it is allowed as long
  7. # as the name is changed.
  8. #
  9. # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  10. #
  11. # 0. You just DO WHATEVER THE FUCK YOU WANT TO. (IT'S SLOPPY CODE ANYWAY)
  12. #
  13. # WARANTIES:
  14. # 0. Are you kidding me?
  15. # 1. Seriously, Are you fucking kidding me?
  16. # 2. If anything goes wrong, sue the "The Empire".
  17. # Note about Summary
  18. # has to be 1 line, no '\n' allowed!
  19. """
  20. Summary: |
  21. some summary ...
  22. Your post
  23. """
  24. """
  25. Everything the Header can't have ":" or "..." in it, you can't have title
  26. with ":" it makes markdown break!
  27. """
  28. """
  29. The content directory can contain only mardown or txt files, no images
  30. allowed!
  31. """
  32. import os
  33. import re
  34. import datetime
  35. import yaml # in debian python-yaml
  36. from StringIO import StringIO
  37. import codecs
  38. from jinja2 import Environment, FileSystemLoader # in debian python-jinja2
  39. import markdown as markdown2
  40. import argparse
  41. import sys
  42. from distutils import dir_util
  43. import shutil
  44. import pdb
  45. CONFIG = {
  46. 'content_root': 'content', # where the markdown files are
  47. 'output_to': 'oz123.github.com',
  48. 'templates': 'templates',
  49. 'date_format': '%Y-%m-%d',
  50. 'base_url': 'oz123.github.com',
  51. 'http_port': 3030,
  52. 'content_encoding': 'utf-8',
  53. }
  54. GLOBAL_TEMPLATE_CONTEXT = {
  55. 'media_base': '/media/',
  56. 'media_url': '../media/',
  57. 'site_url' : 'oz123.github.com',
  58. 'last_build' : datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
  59. 'twitter' : 'https://twitter.com/#!/OzNTiram',
  60. 'stackoverflow': "http://stackoverflow.com/users/492620/oz123",
  61. 'github' : "https://github.com/oz123",
  62. 'side_bar': """
  63. <div id="nav">
  64. <div><img src="/media/img/me.png"></div>
  65. <a title="Home" href="/">home</a>
  66. <a title="About" class="about" href="/about.html">about</a>
  67. <a title="Archive" class="archive" href="/archive">archive</a>
  68. <a title="Atom feeds" href="/atom.xml">atom</a>
  69. <a title="Twitter" href="https://twitter.com/#!/OzNTiram">twitter</a>
  70. <a title="Stackoverflow" href="http://stackoverflow.com/users/492620/oz123">stackoverflow</a>
  71. <a title="Github" href="https://github.com/oz123">github</a>
  72. </div>
  73. """,
  74. 'google_analytics':"""
  75. <script type="text/javascript">
  76. var _gaq = _gaq || [];
  77. _gaq.push(['_setAccount', 'UA-36587163-1']);
  78. _gaq.push(['_trackPageview']);
  79. (function() {
  80. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  81. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  82. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  83. })();
  84. </script>"""
  85. }
  86. KINDS = {
  87. 'writing': {
  88. 'name': 'writing', 'name_plural': 'writings',
  89. },
  90. 'note': {
  91. 'name': 'note', 'name_plural': 'notes',
  92. },
  93. 'link': {
  94. 'name': 'link', 'name_plural': 'links',
  95. },
  96. 'photo': {
  97. 'name': 'photo', 'name_plural': 'photos',
  98. },
  99. 'page': {
  100. 'name': 'page', 'name_plural': 'pages',
  101. },
  102. }
  103. jinja_env = Environment(loader=FileSystemLoader(CONFIG['templates']))
  104. class Tag(object):
  105. def __init__(self, name):
  106. super(Tag, self).__init__()
  107. self.name = name
  108. self.prepare()
  109. self.permalink = "oz123.github.com"
  110. def prepare(self):
  111. _slug = self.name.lower()
  112. _slug = re.sub(r'[;;,. ]', '-', _slug)
  113. self.slug = _slug
  114. class Entry(object):
  115. def __init__(self, path):
  116. super(Entry, self).__init__()
  117. path = path.split('content/')[-1]
  118. self.path = path
  119. self.prepare()
  120. def __str__(self):
  121. return self.path
  122. def __repr__(self):
  123. return self.path
  124. @property
  125. def name(self):
  126. return os.path.splitext(os.path.basename(self.path))[0]
  127. @property
  128. def abspath(self):
  129. return os.path.abspath(os.path.join(CONFIG['content_root'], self.path))
  130. @property
  131. def destination(self):
  132. dest = "%s/%s/index.html" % (KINDS[self.kind]['name_plural'], self.name)
  133. print dest
  134. return os.path.join(CONFIG['output_to'], dest)
  135. @property
  136. def title(self):
  137. return self.header['title']
  138. @property
  139. def summary_html(self):
  140. return "%s" % markdown2.markdown(self.header['summary'].strip())
  141. @property
  142. def credits_html(self):
  143. return "%s" % markdown2.markdown(self.header['credits'].strip())
  144. @property
  145. def summary_atom(self):
  146. summarya=markdown2.markdown(self.header['summary'].strip())
  147. summarya=re.sub("<p>|</p>","",summarya)
  148. more = '<a href="%s"> continue reading...</a>' % (self.permalink)
  149. return summarya+more
  150. @property
  151. def published_html(self):
  152. if self.kind in ['link', 'note', 'photo']:
  153. return self.header['published'].strftime("%B %d, %Y %I:%M %p")
  154. return self.header['published'].strftime("%B %d, %Y")
  155. @property
  156. def published_atom(self):
  157. return self.published.strftime("%Y-%m-%dT%H:%M:%SZ")
  158. @property
  159. def atom_id(self):
  160. return "tag:oz123.github.com,%s:%s" % \
  161. (
  162. self.published.strftime("%Y-%m-%d"),
  163. self.permalink,
  164. )
  165. @property
  166. def body_html(self):
  167. return markdown2.markdown(self.body, extras=['code-color'])
  168. @property
  169. def permalink(self):
  170. return "/%s/%s" % (KINDS[self.kind]['name_plural'], self.name)
  171. @property
  172. def tags(self):
  173. tags = list()
  174. for t in self.header['tags']:
  175. tags.append(Tag(t))
  176. return tags
  177. def prepare(self):
  178. file = codecs.open(self.abspath, 'r')
  179. header = ['---']
  180. while True:
  181. line = file.readline()
  182. line = line.rstrip()
  183. if not line: break
  184. header.append(line)
  185. self.header = yaml.load(StringIO('\n'.join(header)))
  186. for h in self.header.items():
  187. if h:
  188. try:
  189. setattr(self, h[0], h[1])
  190. except:
  191. pass
  192. body = list()
  193. for line in file.readlines():
  194. body.append(line)
  195. self.body = '\n'.join(body)
  196. file.close()
  197. if self.kind == 'link':
  198. from urlparse import urlparse
  199. self.domain_name = urlparse(self.url).netloc
  200. elif self.kind == 'photo':
  201. pass
  202. elif self.kind == 'note':
  203. pass
  204. elif self.kind == 'writing':
  205. pass
  206. def render(self):
  207. if not self.header['public']:
  208. return False
  209. try:
  210. os.makedirs(os.path.dirname(self.destination))
  211. except:
  212. pass
  213. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  214. print "context"
  215. print context
  216. context['entry'] = self
  217. print "entry", context['entry']
  218. template = jinja_env.get_template("entry.html")
  219. print "template" , template
  220. #print dir(template)
  221. #raw_input()
  222. html = template.render(context)
  223. print "in render", self.destination
  224. destination = codecs.open(self.destination, 'w', CONFIG['content_encoding'])
  225. destination.write(html)
  226. destination.close()
  227. return True
  228. class Link(Entry):
  229. def __init__(self, path):
  230. super(Link, self).__init__(path)
  231. @property
  232. def permalink(self):
  233. print "self.url", self.url
  234. raw_input()
  235. return self.url
  236. def entry_factory():
  237. pass
  238. def _sort_entries(entries):
  239. _entries = dict()
  240. sorted_entries = list()
  241. for entry in entries:
  242. _published = entry.header['published'].isoformat()
  243. _entries[_published] = entry
  244. sorted_keys = sorted(_entries.keys())
  245. sorted_keys.reverse()
  246. for key in sorted_keys:
  247. sorted_entries.append(_entries[key])
  248. return sorted_entries
  249. def render_index(entries):
  250. """
  251. this function renders the main page located at index.html
  252. under oz123.github.com
  253. """
  254. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  255. context['entries'] = entries[:10]
  256. template = jinja_env.get_template('entry_index.html')
  257. html = template.render(context)
  258. destination = codecs.open("%s/index.html" % CONFIG['output_to'], 'w', CONFIG['content_encoding'])
  259. destination.write(html)
  260. destination.close()
  261. def render_archive(entries, render_to=None):
  262. """
  263. this function creates the archive page
  264. """
  265. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  266. context['entries'] = entries[10:]
  267. template = jinja_env.get_template('archive_index.html')
  268. html = template.render(context)
  269. if not render_to:
  270. render_to = "%s/archive/index.html" % CONFIG['output_to']
  271. dir_util.mkpath("%s/archive" % CONFIG['output_to'])
  272. destination = codecs.open("%s/archive/index.html" % CONFIG['output_to'], 'w', CONFIG['content_encoding'])
  273. destination.write(html)
  274. destination.close()
  275. def render_atom_feed(entries, render_to=None):
  276. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  277. context['entries'] = entries[:10]
  278. template = jinja_env.get_template('atom.xml')
  279. html = template.render(context)
  280. if not render_to:
  281. render_to = "%s/atom.xml" % CONFIG['output_to']
  282. destination = codecs.open(render_to, 'w', CONFIG['content_encoding'])
  283. destination.write(html)
  284. destination.close()
  285. def render_tag_pages(tag_tree):
  286. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  287. for t in tag_tree.items():
  288. context['tag'] = t[1]['tag']
  289. context['entries'] = _sort_entries(t[1]['entries'])
  290. destination = "%s/tags/%s" % (CONFIG['output_to'], context['tag'].slug)
  291. try:
  292. os.makedirs(destination)
  293. except:
  294. pass
  295. template = jinja_env.get_template('tag_index.html')
  296. html = template.render(context)
  297. file = codecs.open("%s/index.html" % destination, 'w', CONFIG['content_encoding'])
  298. file.write(html)
  299. file.close()
  300. #print " tags/%s" % (context['tag'].slug, )
  301. render_atom_feed(context['entries'], render_to="%s/atom.xml" % destination)
  302. def build():
  303. print
  304. print "Rendering website now..."
  305. print
  306. print " entries:"
  307. entries = list()
  308. tags = dict()
  309. for root, dirs, files in os.walk(CONFIG['content_root']):
  310. for fileName in files:
  311. try:
  312. entry = Entry(os.path.join(root, fileName))
  313. except Exception, e:
  314. print "Found some problem in: ", fileName
  315. print e
  316. raw_input("Please correct")
  317. sys.exit()
  318. if entry.render():
  319. entries.append(entry)
  320. for tag in entry.tags:
  321. if not tags.has_key(tag.name):
  322. tags[tag.name] = {
  323. 'tag': tag,
  324. 'entries': list(),
  325. }
  326. tags[tag.name]['entries'].append(entry)
  327. print " %s" % entry.path
  328. print " :done"
  329. print
  330. print " tag pages & their atom feeds:"
  331. render_tag_pages(tags)
  332. print " :done"
  333. print
  334. print " site wide index"
  335. entries = _sort_entries(entries)
  336. #render_index(_sort_entries(entries))
  337. render_index(entries)
  338. print "................done"
  339. print " archive index"
  340. render_archive(entries)
  341. print "................done"
  342. print " site wide atom feeds"
  343. #render_atom_feed(_sort_entries(entries))
  344. render_atom_feed(entries)
  345. print "...........done"
  346. print
  347. print "All done "
  348. def preview(PREVIEW_ADDR = '127.0.1.1',PREVIEW_PORT = 11000):
  349. """
  350. launch an HTTP to preview the website
  351. """
  352. import SimpleHTTPServer
  353. import SocketServer
  354. Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
  355. httpd = SocketServer.TCPServer(("", CONFIG['http_port']), Handler)
  356. os.chdir(CONFIG['output_to'])
  357. print "and ready to test at http://127.0.0.1:%d" % CONFIG['http_port']
  358. print "Hit Ctrl+C to exit"
  359. try:
  360. httpd.serve_forever()
  361. except KeyboardInterrupt:
  362. print
  363. print "Shutting Down... Bye!."
  364. print
  365. httpd.server_close()
  366. def publish(GITDIRECTORY="oz123.github.com"):
  367. pass
  368. def clean(GITDIRECTORY="oz123.github.com"):
  369. directoriestoclean=["writings", "notes", "links", "tags", "archive"]
  370. os.chdir(GITDIRECTORY)
  371. for directory in directoriestoclean:
  372. shutil.rmtree(directory)
  373. if __name__== '__main__':
  374. parser = argparse.ArgumentParser(description='blogit - a tool blog on github.')
  375. parser.add_argument('-b','--build', action="store_true",
  376. help='convert the markdown files to HTML')
  377. parser.add_argument('-p','--preview', action="store_true",
  378. help='Launch HTTP server to preview the website')
  379. parser.add_argument('-c','--clean', action="store_true",
  380. help='cleanoutputfiles')
  381. args = parser.parse_args()
  382. if len(sys.argv) < 2 :
  383. parser.print_help()
  384. sys.exit()
  385. #import pdb; pdb.set_trace()
  386. if args.clean:
  387. clean()
  388. if args.build:
  389. #pdb.set_trace()
  390. build()
  391. if args.preview:
  392. preview()