blogit.py 14 KB

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