blogit.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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. try:
  40. import markdown2
  41. except ImportError:
  42. import markdown as markdown2
  43. import argparse
  44. import sys
  45. from distutils import dir_util
  46. import shutil
  47. CONFIG = {
  48. 'content_root': 'content', # where the markdown files are
  49. 'output_to': 'oz123.github.com',
  50. 'templates': 'templates',
  51. 'date_format': '%Y-%m-%d',
  52. 'base_url': 'http://oz123.github.com',
  53. 'http_port': 3030,
  54. 'content_encoding': 'utf-8',
  55. }
  56. GLOBAL_TEMPLATE_CONTEXT = {
  57. 'media_base': '/media/',
  58. 'media_url': '../media/',
  59. 'site_url' : 'http://oz123.github.com',
  60. 'last_build' : datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
  61. 'twitter' : 'https://twitter.com/#!/OzNTiram',
  62. 'stackoverflow': "http://stackoverflow.com/users/492620/oz123",
  63. 'github' : "https://github.com/oz123",
  64. 'side_bar': """
  65. <div id="nav">
  66. <div><img src="/media/img/me.png"></div>
  67. <a title="Home" href="/">home</a>
  68. <a title="About" class="about" href="/about.html">about</a>
  69. <a title="Archive" class="archive" href="/archive">archive</a>
  70. <a title="Atom feeds" href="/atom.xml">atom</a>
  71. <a title="Twitter" href="https://twitter.com/#!/OzNTiram">twitter</a>
  72. <a title="Stackoverflow" href="http://stackoverflow.com/users/492620/oz123">stackoverflow</a>
  73. <a title="Github" href="https://github.com/oz123">github</a>
  74. <script type="text/javascript"><!--
  75. google_ad_client = "ca-pub-2570499281263620";
  76. /* new_tower_for_oz123githubcom */
  77. google_ad_slot = "8107518414";
  78. google_ad_width = 120;
  79. google_ad_height = 600;
  80. //-->
  81. </script>
  82. <script type="text/javascript"
  83. src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
  84. </script>
  85. </div>
  86. """,
  87. 'google_analytics':"""
  88. <script type="text/javascript">
  89. var _gaq = _gaq || [];
  90. _gaq.push(['_setAccount', 'UA-36587163-1']);
  91. _gaq.push(['_trackPageview']);
  92. (function() {
  93. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  94. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  95. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  96. })();
  97. </script>""",
  98. 'disquss' : """<div id="disqus_thread"></div>
  99. <script type="text/javascript">
  100. /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
  101. var disqus_shortname = 'oz123githubcom'; // required: replace example with your forum shortname
  102. /* * * DON'T EDIT BELOW THIS LINE * * */
  103. (function() {
  104. var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
  105. dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
  106. (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
  107. })();
  108. </script>
  109. <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
  110. <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
  111. """
  112. }
  113. KINDS = {
  114. 'writing': {
  115. 'name': 'writing', 'name_plural': 'writings',
  116. },
  117. 'note': {
  118. 'name': 'note', 'name_plural': 'notes',
  119. },
  120. 'link': {
  121. 'name': 'link', 'name_plural': 'links',
  122. },
  123. 'photo': {
  124. 'name': 'photo', 'name_plural': 'photos',
  125. },
  126. 'page': {
  127. 'name': 'page', 'name_plural': 'pages',
  128. },
  129. }
  130. jinja_env = Environment(loader=FileSystemLoader(CONFIG['templates']))
  131. class Tag(object):
  132. def __init__(self, name):
  133. super(Tag, self).__init__()
  134. self.name = name
  135. self.prepare()
  136. self.permalink = GLOBAL_TEMPLATE_CONTEXT["site_url"]
  137. def prepare(self):
  138. _slug = self.name.lower()
  139. _slug = re.sub(r'[;;,. ]', '-', _slug)
  140. self.slug = _slug
  141. class Entry(object):
  142. def __init__(self, path):
  143. super(Entry, self).__init__()
  144. path = path.split('content/')[-1]
  145. self.path = path
  146. self.prepare()
  147. def __str__(self):
  148. return self.path
  149. def __repr__(self):
  150. return self.path
  151. @property
  152. def name(self):
  153. return os.path.splitext(os.path.basename(self.path))[0]
  154. @property
  155. def abspath(self):
  156. return os.path.abspath(os.path.join(CONFIG['content_root'], self.path))
  157. @property
  158. def destination(self):
  159. dest = "%s/%s/index.html" % (KINDS[self.kind]['name_plural'], self.name)
  160. print dest
  161. return os.path.join(CONFIG['output_to'], dest)
  162. @property
  163. def title(self):
  164. return self.header['title']
  165. @property
  166. def summary_html(self):
  167. return "%s" % markdown2.markdown(self.header['summary'].strip())
  168. @property
  169. def credits_html(self):
  170. return "%s" % markdown2.markdown(self.header['credits'].strip())
  171. @property
  172. def summary_atom(self):
  173. summarya=markdown2.markdown(self.header['summary'].strip())
  174. summarya=re.sub("<p>|</p>","",summarya)
  175. more = '<a href="%s"> continue reading...</a>' % (self.permalink)
  176. return summarya+more
  177. @property
  178. def published_html(self):
  179. if self.kind in ['link', 'note', 'photo']:
  180. return self.header['published'].strftime("%B %d, %Y %I:%M %p")
  181. return self.header['published'].strftime("%B %d, %Y")
  182. @property
  183. def published_atom(self):
  184. return self.published.strftime("%Y-%m-%dT%H:%M:%SZ")
  185. @property
  186. def atom_id(self):
  187. return "tag:%s,%s:%s" % \
  188. (
  189. self.published.strftime("%Y-%m-%d"),
  190. self.permalink,
  191. GLOBAL_TEMPLATE_CONTEXT["site_url"]
  192. )
  193. @property
  194. def body_html(self):
  195. return markdown2.markdown(self.body)#, extras=['code-color'])
  196. @property
  197. def permalink(self):
  198. return "/%s/%s" % (KINDS[self.kind]['name_plural'], self.name)
  199. @property
  200. def tags(self):
  201. tags = list()
  202. for t in self.header['tags']:
  203. tags.append(Tag(t))
  204. return tags
  205. def prepare(self):
  206. file = codecs.open(self.abspath, 'r')
  207. header = ['---']
  208. while True:
  209. line = file.readline()
  210. line = line.rstrip()
  211. if not line: break
  212. header.append(line)
  213. self.header = yaml.load(StringIO('\n'.join(header)))
  214. for h in self.header.items():
  215. if h:
  216. try:
  217. setattr(self, h[0], h[1])
  218. except:
  219. pass
  220. body = list()
  221. for line in file.readlines():
  222. body.append(line)
  223. self.body = ''.join(body)
  224. file.close()
  225. if self.kind == 'link':
  226. from urlparse import urlparse
  227. self.domain_name = urlparse(self.url).netloc
  228. elif self.kind == 'photo':
  229. pass
  230. elif self.kind == 'note':
  231. pass
  232. elif self.kind == 'writing':
  233. pass
  234. def render(self):
  235. if not self.header['public']:
  236. return False
  237. try:
  238. os.makedirs(os.path.dirname(self.destination))
  239. except:
  240. pass
  241. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  242. context['entry'] = self
  243. template = jinja_env.get_template("entry.html")
  244. html = template.render(context)
  245. destination = codecs.open(self.destination, 'w', CONFIG['content_encoding'])
  246. destination.write(html)
  247. destination.close()
  248. return True
  249. class Link(Entry):
  250. def __init__(self, path):
  251. super(Link, self).__init__(path)
  252. @property
  253. def permalink(self):
  254. print "self.url", self.url
  255. raw_input()
  256. return self.url
  257. def entry_factory():
  258. pass
  259. def _sort_entries(entries):
  260. _entries = dict()
  261. sorted_entries = list()
  262. for entry in entries:
  263. _published = entry.header['published'].isoformat()
  264. _entries[_published] = entry
  265. sorted_keys = sorted(_entries.keys())
  266. sorted_keys.reverse()
  267. for key in sorted_keys:
  268. sorted_entries.append(_entries[key])
  269. return sorted_entries
  270. def render_index(entries):
  271. """
  272. this function renders the main page located at index.html
  273. under oz123.github.com
  274. """
  275. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  276. context['entries'] = entries[:10]
  277. template = jinja_env.get_template('entry_index.html')
  278. html = template.render(context)
  279. destination = codecs.open("%s/index.html" % CONFIG['output_to'], 'w', CONFIG['content_encoding'])
  280. destination.write(html)
  281. destination.close()
  282. def render_archive(entries, render_to=None):
  283. """
  284. this function creates the archive page
  285. """
  286. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  287. context['entries'] = entries[10:]
  288. template = jinja_env.get_template('archive_index.html')
  289. html = template.render(context)
  290. if not render_to:
  291. render_to = "%s/archive/index.html" % CONFIG['output_to']
  292. dir_util.mkpath("%s/archive" % CONFIG['output_to'])
  293. destination = codecs.open("%s/archive/index.html" % CONFIG['output_to'], 'w', CONFIG['content_encoding'])
  294. destination.write(html)
  295. destination.close()
  296. def render_atom_feed(entries, render_to=None):
  297. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  298. context['entries'] = entries[:10]
  299. template = jinja_env.get_template('atom.xml')
  300. html = template.render(context)
  301. if not render_to:
  302. render_to = "%s/atom.xml" % CONFIG['output_to']
  303. destination = codecs.open(render_to, 'w', CONFIG['content_encoding'])
  304. destination.write(html)
  305. destination.close()
  306. def render_tag_pages(tag_tree):
  307. context = GLOBAL_TEMPLATE_CONTEXT.copy()
  308. for t in tag_tree.items():
  309. context['tag'] = t[1]['tag']
  310. context['entries'] = _sort_entries(t[1]['entries'])
  311. destination = "%s/tags/%s" % (CONFIG['output_to'], context['tag'].slug)
  312. try:
  313. os.makedirs(destination)
  314. except:
  315. pass
  316. template = jinja_env.get_template('tag_index.html')
  317. html = template.render(context)
  318. file = codecs.open("%s/index.html" % destination, 'w', CONFIG['content_encoding'])
  319. file.write(html)
  320. file.close()
  321. render_atom_feed(context['entries'], render_to="%s/atom.xml" % destination)
  322. def build():
  323. print
  324. print "Rendering website now..."
  325. print
  326. print " entries:"
  327. entries = list()
  328. tags = dict()
  329. for root, dirs, files in os.walk(CONFIG['content_root']):
  330. for fileName in files:
  331. try:
  332. entry = Entry(os.path.join(root, fileName))
  333. except Exception, e:
  334. print "Found some problem in: ", fileName
  335. print e
  336. raw_input("Please correct")
  337. sys.exit()
  338. if entry.render():
  339. entries.append(entry)
  340. for tag in entry.tags:
  341. if not tags.has_key(tag.name):
  342. tags[tag.name] = {
  343. 'tag': tag,
  344. 'entries': list(),
  345. }
  346. tags[tag.name]['entries'].append(entry)
  347. print " %s" % entry.path
  348. print " :done"
  349. print
  350. print " tag pages & their atom feeds:"
  351. render_tag_pages(tags)
  352. print " :done"
  353. print
  354. print " site wide index"
  355. entries = _sort_entries(entries)
  356. render_index(entries)
  357. print "................done"
  358. print " archive index"
  359. render_archive(entries)
  360. print "................done"
  361. print " site wide atom feeds"
  362. render_atom_feed(entries)
  363. print "...........done"
  364. print
  365. print "All done "
  366. def preview(PREVIEW_ADDR = '127.0.1.1',PREVIEW_PORT = 11000):
  367. """
  368. launch an HTTP to preview the website
  369. """
  370. import SimpleHTTPServer
  371. import SocketServer
  372. Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
  373. httpd = SocketServer.TCPServer(("", CONFIG['http_port']), Handler)
  374. os.chdir(CONFIG['output_to'])
  375. print "and ready to test at http://127.0.0.1:%d" % CONFIG['http_port']
  376. print "Hit Ctrl+C to exit"
  377. try:
  378. httpd.serve_forever()
  379. except KeyboardInterrupt:
  380. print
  381. print "Shutting Down... Bye!."
  382. print
  383. httpd.server_close()
  384. def publish(GITDIRECTORY="oz123.github.com"):
  385. pass
  386. def clean(GITDIRECTORY="oz123.github.com"):
  387. directoriestoclean=["writings", "notes", "links", "tags", "archive"]
  388. os.chdir(GITDIRECTORY)
  389. for directory in directoriestoclean:
  390. shutil.rmtree(directory)
  391. def dist(SOURCEDIR="/home/ozn/blogit/content/",DESTDIR="oz123.github.com/writings_raw/content/"):
  392. """
  393. sync raw files from SOURCE to DEST
  394. """
  395. import subprocess as sp
  396. sp.call(["rsync", "-av", SOURCEDIR, DESTDIR], shell=False, cwd=os.getcwd())
  397. if __name__== '__main__':
  398. parser = argparse.ArgumentParser(description='blogit - a tool blog on github.')
  399. parser.add_argument('-b','--build', action="store_true",
  400. help='convert the markdown files to HTML')
  401. parser.add_argument('-p','--preview', action="store_true",
  402. help='Launch HTTP server to preview the website')
  403. parser.add_argument('-c','--clean', action="store_true",
  404. help='clean output files')
  405. parser.add_argument('-d','--dist', action="store_true",
  406. help='sync raw files from SOURCE to DEST')
  407. args = parser.parse_args()
  408. if len(sys.argv) < 2 :
  409. parser.print_help()
  410. sys.exit()
  411. if args.clean:
  412. clean()
  413. if args.build:
  414. build()
  415. if args.dist:
  416. dist()
  417. if args.preview:
  418. preview()