gitoyen.py 4.2 KB

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