Guest User

Untitled

a guest
Sep 22nd, 2014
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.11 KB | None | 0 0
  1. #
  2. #Static blogs generator.
  3. #See https://github.com/st-kurilin/blogator for details.
  4. #
  5. #Main script. Used to build final script using build.py script.
  6. #
  7.  
  8. ###Files operations
  9. #separated to make testing easier
  10.  
  11. """Default values for some files.
  12. Used to distribute script as a single file.
  13. Actual values filled by build.py script."""
  14. PREDEFINED = {}
  15.  
  16. def read(path):
  17. """Reads file content from FS"""
  18. if path in PREDEFINED:
  19. return PREDEFINED[path]
  20. with open(path.as_posix()) as file:
  21. return file.read()
  22.  
  23. def write(path, content):
  24. """Writes file content to FS"""
  25. with open(path.as_posix(), 'w') as file:
  26. file.write(content)
  27.  
  28. def copy(from_p, to_p):
  29. """Copies file content"""
  30. import shutil
  31. shutil.copyfile(from_p.as_posix(), to_p.as_posix())
  32.  
  33. def file_exist(path):
  34. """Check if file exist for specified path"""
  35. return path.is_file()
  36.  
  37.  
  38. ###Markdown template engine operations
  39. def md_read(inp):
  40. """Reads markdown formatted message."""
  41. import markdown
  42. md_converter = markdown.Markdown(extensions=['meta'])
  43. content = md_converter.convert(inp)
  44. meta = getattr(md_converter, 'Meta', [])
  45. return {
  46. 'meta' : meta,
  47. 'content' : content
  48. }
  49.  
  50. def md_meta_get(meta, key, alt=None, single_value=True):
  51. """Reads value from markdown read message meta."""
  52. if key in meta:
  53. if single_value and meta[key]:
  54. return meta[key][0]
  55. else:
  56. return meta[key]
  57. return alt
  58.  
  59.  
  60. ###Pystache template engine operations
  61. def pystached(template, data):
  62. """Applies data to pystache template"""
  63. import pystache
  64. pys_template = pystache.parse(template)
  65. pys_renderer = pystache.Renderer()
  66. return pys_renderer.render(pys_template, data)
  67.  
  68.  
  69. ###Meta files readers operations
  70. def parse_blog_meta(blog_meta_content):
  71. """Reads general blog info from file."""
  72. from functools import partial
  73. meta = md_read(blog_meta_content)['meta']
  74. get = partial(md_meta_get, meta)
  75. favicon_file = get('favicon-file')
  76. favicon_url = get('favicon-url', 'favicon.cc/favicon/169/1/favicon.png')
  77. return {
  78. 'meta' : meta,
  79. 'title' : get('title', 'Blog'),
  80. 'annotation' : get('annotation', 'Blogging for living'),
  81. 'favicon-file' : favicon_file,
  82. 'favicon' : 'favicon.ico' if favicon_file else favicon_url,
  83. 'posts' : get('posts', [], False),
  84. 'disqus' : get('disqus'),
  85. 'ganalitics' : get('ganalitics'),
  86. }
  87.  
  88. def parse_post(post_blob, post_blob_orig_name):
  89. """Reads post info from file."""
  90. import datetime
  91. from functools import partial
  92.  
  93. def reformat_date(inpf, outf, date):
  94. """Reformats dates from one specified format to other one."""
  95. if date is None:
  96. return None
  97. return datetime.datetime.strptime(date, inpf).strftime(outf)
  98.  
  99. row_post = md_read(post_blob)
  100. post = {}
  101. post['meta'] = meta = row_post['meta']
  102. get = partial(md_meta_get, meta)
  103. post['content'] = row_post['content']
  104. post['title'] = get('title', post_blob_orig_name)
  105. post['brief'] = get('brief')
  106. post['short_title'] = get('short_title', post['title'])
  107. post['link_base'] = get('link', post_blob_orig_name + ".html")
  108. post['link'] = './' + post['link_base']
  109. post['published'] = reformat_date('%Y-%m-%d', '%d %b %Y',
  110. get('published'))
  111. return post
  112.  
  113.  
  114. ###Flow operations
  115. def clean_target(target):
  116. """Cleans target directory. Hidden files will not be deleted."""
  117. import os
  118. import glob
  119. tpath = target.as_posix()
  120. if not os.path.exists(tpath):
  121. os.makedirs(tpath)
  122. for file in glob.glob(tpath + '/*'):
  123. os.remove(file)
  124.  
  125. def generate(blog_path, templates, target):
  126. """Generates blog content. Target directory expected to be empty."""
  127.  
  128. def prepare_favicon(blog, blog_home_dir, target):
  129. """Puts favicon file in right place with right name."""
  130. if blog['favicon-file']:
  131. orig_path = blog_home_dir / blog['favicon-file']
  132. destination_path = target / 'favicon.ico'
  133. copy(orig_path, destination_path)
  134.  
  135. def marked_active_post(orig_posts, active_index):
  136. """Returns copy of original posts
  137. with specified post marked as active"""
  138. active_post = orig_posts[active_index]
  139. posts_view = orig_posts.copy()
  140. active_post = orig_posts[active_index].copy()
  141. active_post['active?'] = True
  142. posts_view[active_index] = active_post
  143. return posts_view
  144.  
  145. def write_templated(template_path, out_path, data):
  146. """Generate templated content to file."""
  147. write(out_path, pystached(read(template_path), data))
  148.  
  149. def fname(file_name):
  150. """file name without extension"""
  151. from pathlib import Path
  152. as_path = Path(file_name)
  153. name = as_path.name
  154. suffix = as_path.suffix
  155. return name.rsplit(suffix, 1)[0]
  156.  
  157. def read_post(declared_path, work_dir):
  158. """Find location of post and read it"""
  159. from pathlib import Path
  160. candidates = [work_dir / declared_path, Path(declared_path)]
  161. for candidate in candidates:
  162. if file_exist(candidate):
  163. return read(candidate)
  164. raise NameError("Tried find post file by [{}] but didn't find anything"
  165. .format(', '.join([_.as_posix() for _ in candidates])))
  166.  
  167. blog = parse_blog_meta(read(blog_path))
  168. work_dir = blog_path.parent
  169. prepare_favicon(blog, work_dir, target)
  170. posts = [parse_post(read_post(_, work_dir), fname(_))
  171. for _ in blog['posts']]
  172. for active_index, post in enumerate(posts):
  173. posts_view = marked_active_post(posts, active_index)
  174. write_templated(templates / "post.template.html",
  175. target / post['link_base'],
  176. {'blog': blog, 'posts': posts_view, 'post': post})
  177.  
  178. write_templated(templates / "index.template.html",
  179. target / "index.html",
  180. {'blog' : blog, 'posts': posts})
  181.  
  182.  
  183. ###Utils
  184. def create_parser():
  185. """Parser factory method."""
  186. import argparse
  187. from pathlib import Path
  188.  
  189. parser = argparse.ArgumentParser(description='''Generates static blog
  190. content from markdown posts.
  191. ''')
  192. parser.add_argument('blog',
  193. type=Path,
  194. help='File with general information about blog',
  195. default='blog')
  196. parser.add_argument('-target',
  197. type=Path,
  198. help='generated content destination',
  199. default='target')
  200. parser.add_argument('-templates',
  201. type=Path,
  202. help='directory with templates',
  203. default='blogtor-virtual/templates')
  204. return parser
  205.  
  206.  
  207. def main():
  208. """Start endpoint"""
  209. args = create_parser().parse_args()
  210. clean_target(args.target)
  211. generate(args.blog, args.templates, args.target)
  212.  
  213. ###Files operations
  214. #separated to make testing easier
  215.  
  216. """Default values for some files.
  217. Used to distribute script as a single file.
  218. Actual values filled by build.py script."""
  219. PREDEFINED = {}
  220.  
  221. def read(path):
  222. """Reads file content from FS"""
  223. if path in PREDEFINED:
  224. return PREDEFINED[path]
  225. with open(path.as_posix()) as file:
  226. return file.read()
  227.  
  228. def write(path, content):
  229. """Writes file content to FS"""
  230. with open(path.as_posix(), 'w') as file:
  231. file.write(content)
  232.  
  233. def copy(from_p, to_p):
  234. """Copies file content"""
  235. import shutil
  236. shutil.copyfile(from_p.as_posix(), to_p.as_posix())
  237.  
  238. def file_exist(path):
  239. """Check if file exist for specified path"""
  240. return path.is_file()
  241.  
  242. ###Markdown template engine operations
  243. def md_read(inp):
  244. """Reads markdown formatted message."""
  245. import markdown
  246. md_converter = markdown.Markdown(extensions=['meta'])
  247. content = md_converter.convert(inp)
  248. meta = getattr(md_converter, 'Meta', [])
  249.  
  250. return {
  251. 'meta' : meta,
  252. 'content' : content
  253. }
  254.  
  255. def md_meta_get(meta, key, alt=None, single_value=True):
  256. """Reads value from markdown read message meta."""
  257. if key in meta:
  258. if single_value and meta[key]:
  259. return meta[key][0]
  260. else:
  261. return meta[key]
  262. return alt
  263.  
  264. ###Pystache template engine operations
  265. def pystached(template, data):
  266. """Applies data to pystache template"""
  267. import pystache
  268. pys_template = pystache.parse(template)
  269. pys_renderer = pystache.Renderer()
  270. return pys_renderer.render(pys_template, data)
  271.  
  272. ###Meta files readers operations
  273. def parse_blog_meta(blog_meta_content):
  274. """Reads general blog info from file."""
  275. from functools import partial
  276. meta = md_read(blog_meta_content)['meta']
  277. get = partial(md_meta_get, meta)
  278.  
  279. favicon_file = get('favicon-file')
  280. favicon_url = get('favicon-url', 'favicon.cc/favicon/169/1/favicon.png')
  281. return {
  282. 'meta' : meta,
  283. 'title' : get('title', 'Blog'),
  284. 'annotation' : get('annotation', 'Blogging for living'),
  285. 'favicon-file' : favicon_file,
  286. 'favicon' : 'favicon.ico' if favicon_file else favicon_url,
  287. 'posts' : get('posts', [], False),
  288. 'disqus' : get('disqus'),
  289. 'ganalitics' : get('ganalitics'),
  290. }
  291.  
  292. def parse_post(post_blob, post_blob_orig_name):
  293. """Reads post info from file."""
  294. import datetime
  295. from functools import partial
  296.  
  297. def reformat_date(inpf, outf, date):
  298. """Reformats dates from one specified format to other one."""
  299. if date is None:
  300. return None
  301. return datetime.datetime.strptime(date, inpf).strftime(outf)
  302.  
  303. row_post = md_read(post_blob)
  304. post = {}
  305. post['meta'] = meta = row_post['meta']
  306. get = partial(md_meta_get, meta)
  307. post['content'] = row_post['content']
  308. post['title'] = get('title', post_blob_orig_name)
  309. post['brief'] = get('brief')
  310. post['short_title'] = get('short_title', post['title'])
  311. post['link_base'] = get('link', post_blob_orig_name + ".html")
  312. post['link'] = './' + post['link_base']
  313.  
  314. post['published'] = reformat_date('%Y-%m-%d', '%d %b %Y',
  315. get('published'))
  316. return post
  317.  
  318.  
  319. ###Flow operations
  320. def clean_target(target):
  321. """Cleans target directory. Hidden files will not be deleted."""
  322. import os
  323. import glob
  324. tpath = target.as_posix()
  325. if not os.path.exists(tpath):
  326. os.makedirs(tpath)
  327. for file in glob.glob(tpath + '/*'):
  328. os.remove(file)
  329.  
  330. def generate(blog_path, templates, target):
  331. """Generates blog content. Target directory expected to be empty."""
  332.  
  333. def prepare_favicon(blog, blog_home_dir, target):
  334. """Puts favicon file in right place with right name."""
  335. if blog['favicon-file']:
  336. orig_path = blog_home_dir / blog['favicon-file']
  337. destination_path = target / 'favicon.ico'
  338. copy(orig_path, destination_path)
  339.  
  340. def marked_active_post(orig_posts, active_index):
  341. """Returns copy of original posts
  342. with specified post marked as active"""
  343. active_post = orig_posts[active_index]
  344. posts_view = orig_posts.copy()
  345. active_post = orig_posts[active_index].copy()
  346. active_post['active?'] = True
  347. posts_view[active_index] = active_post
  348. return posts_view
  349.  
  350. def write_templated(template_path, out_path, data):
  351. """Generate templated content to file."""
  352. write(out_path, pystached(read(template_path), data))
  353.  
  354. def fname(file_name):
  355. """file name without extension"""
  356. from pathlib import Path
  357. as_path = Path(file_name)
  358. name = as_path.name
  359. suffix = as_path.suffix
  360. return name.rsplit(suffix, 1)[0]
  361.  
  362. def read_post(declared_path, work_dir):
  363. """Find location of post and read it"""
  364. from pathlib import Path
  365. candidates = [work_dir / declared_path, Path(declared_path)]
  366. for candidate in candidates:
  367. if file_exist(candidate):
  368. return read(candidate)
  369. raise NameError("Tried find post file by [{}] but didn't find anything"
  370. .format(', '.join([_.as_posix() for _ in candidates])))
  371.  
  372. blog = parse_blog_meta(read(blog_path))
  373. work_dir = blog_path.parent
  374. prepare_favicon(blog, work_dir, target)
  375. posts = [parse_post(read_post(_, work_dir), fname(_))
  376. for _ in blog['posts']]
  377. for active_index, post in enumerate(posts):
  378. posts_view = marked_active_post(posts, active_index)
  379. write_templated(templates / "post.template.html",
  380. target / post['link_base'],
  381. {'blog': blog, 'posts': posts_view, 'post': post})
  382.  
  383. write_templated(templates / "index.template.html",
  384. target / "index.html",
  385. {'blog' : blog, 'posts': posts})
  386.  
  387.  
  388. ###Utils
  389. def create_parser():
  390. """Parser factory method."""
  391. import argparse
  392. ...
  393.  
  394. def main():
  395. """Start endpoint"""
  396. args = create_parser().parse_args()
  397. clean_target(args.target)
  398. generate(args.blog, args.templates, args.target)
Add Comment
Please, Sign In to add comment