gitoyen.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #! /usr/bin/env python
  2. # coding: utf8
  3. import click
  4. import livereload
  5. import jinja2
  6. import path
  7. import pelican.utils as utils
  8. import datetime
  9. import mimetypes
  10. import subprocess
  11. import SimpleHTTPServer
  12. import SocketServer
  13. PELICAN_CONF = 'pelicanconf.py'
  14. OUTPUT = 'output'
  15. CONTENT = 'content'
  16. BLOG = 'content/blog'
  17. THEME = './theme'
  18. class Config(object):
  19. def __init__(self):
  20. self.settings = path.Path(PELICAN_CONF)
  21. self.output = path.Path(OUTPUT)
  22. self.content = path.Path(CONTENT)
  23. self.blog = path.Path(BLOG)
  24. self.theme = path.Path(THEME)
  25. self.jinja = jinja2.Environment(
  26. loader=jinja2.PackageLoader('gitoyen', 'templates')
  27. )
  28. class Application(object):
  29. def __init__(self, config):
  30. self.config = config
  31. self.chunk_size = 64 * 1024
  32. def _not_found(self, sr):
  33. sr('404 NOT FOUND', [('Content-Type', 'text/plain')])
  34. return ['Not Found']
  35. def _serve_file(self, f, sr):
  36. mime, _ = mimetypes.guess_type(f)
  37. sr('200 OK', [('Content-Type', mime or 'application/octet-stream')])
  38. return f.chunks(self.chunk_size, 'rb')
  39. def _list_dir(self, d, sr):
  40. sr('200 OK', [('Content-Type', 'text/html')])
  41. context = {
  42. 'directory': d.relpath(self.config.output),
  43. 'links': [f.relpath(d) for f in d.listdir()]
  44. }
  45. return (self.config.jinja.get_template('list_dir.tplt')
  46. .stream(**context))
  47. def __call__(self, env, start_response):
  48. path = self.config.output + env.get('PATH_INFO')
  49. path_index = path + 'index.html'
  50. if not path.exists():
  51. return self._not_found(start_response)
  52. if path.isfile():
  53. return self._serve_file(path, start_response)
  54. if path_index.exists():
  55. return self._serve_file(path_index, start_response)
  56. return self._list_dir(path, start_response)
  57. pass_config = click.make_pass_decorator(Config, ensure=True)
  58. @click.group()
  59. def cli():
  60. pass
  61. @cli.command()
  62. @pass_config
  63. def clean(config):
  64. '''Clean the "output" directory'''
  65. click.echo('Clean output directory')
  66. for dir in config.output.dirs():
  67. dir.rmtree()
  68. for file in config.output.files():
  69. file.remove()
  70. @cli.command()
  71. @pass_config
  72. def build(config):
  73. '''Build the pelican static site'''
  74. subprocess.call(['pelican', '-s', config.settings])
  75. @cli.command()
  76. @click.pass_context
  77. def rebuild(ctx):
  78. '''Run clean then build'''
  79. ctx.invoke(clean)
  80. ctx.invoke(build)
  81. @cli.command()
  82. @click.option('--no-debug', is_flag=True, help='remove the debug output')
  83. @click.option('--no-lr', is_flag=True, help='remove the livereload support')
  84. @click.argument('port', default=8000, required=False)
  85. @pass_config
  86. @click.pass_context
  87. def serve(ctx, config, no_debug, no_lr, port):
  88. '''Serve the 'output' directory, default port is 8000'''
  89. if port < 1024 or port > 65535:
  90. error_msg = 'port must be an integer between 1024 and 65535'
  91. raise click.BadParameter(error_msg, param_hint='port')
  92. debug = not no_debug
  93. inner_build = lambda: ctx.invoke(build)
  94. inner_build()
  95. if no_lr:
  96. with config.output:
  97. Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
  98. httpd = SocketServer.TCPServer(('', port), Handler)
  99. click.echo('serving at port %d' % port)
  100. httpd.serve_forever()
  101. else:
  102. server = livereload.Server(Application(config))
  103. server.watch(config.content, inner_build)
  104. server.watch(config.theme, inner_build)
  105. server.serve(port=port, debug=debug)
  106. @cli.command()
  107. @click.argument('title')
  108. @pass_config
  109. def new_post(config, title):
  110. '''Create a new blog entry'''
  111. date = datetime.date.today().isoformat()
  112. filename = '.'.join([date, utils.slugify(title), 'md'])
  113. filename = config.blog / filename
  114. click.echo('Create new post: %s' % click.style(filename, fg='green'))
  115. (config.jinja.get_template('new_post.tplt')
  116. .stream(title=title)
  117. .dump(filename, 'utf8'))
  118. @cli.command()
  119. @pass_config
  120. @click.pass_context
  121. def publish(ctx, config):
  122. ctx.invoke(build)
  123. subprocess.call(['ghp-import', config.output])
  124. subprocess.call(['git', 'push', 'site', '-f', 'gh-pages:master'])
  125. if __name__ == '__main__':
  126. cli()