#! /usr/bin/env python # coding: utf8 import click import livereload import jinja2 import path import pelican.utils as utils import datetime import mimetypes import subprocess import SimpleHTTPServer import SocketServer PELICAN_CONF = 'pelicanconf.py' OUTPUT = 'output' CONTENT = 'content' BLOG = 'content/blog' THEME = './theme' class Config(object): def __init__(self): self.settings = path.Path(PELICAN_CONF) self.output = path.Path(OUTPUT) self.content = path.Path(CONTENT) self.blog = path.Path(BLOG) self.theme = path.Path(THEME) self.jinja = jinja2.Environment( loader=jinja2.PackageLoader('gitoyen', 'templates') ) class Application(object): def __init__(self, config): self.config = config self.chunk_size = 64 * 1024 def _not_found(self, sr): sr('404 NOT FOUND', [('Content-Type', 'text/plain')]) return ['Not Found'] def _serve_file(self, f, sr): mime, _ = mimetypes.guess_type(f) sr('200 OK', [('Content-Type', mime or 'application/octet-stream')]) return f.chunks(self.chunk_size, 'rb') def _list_dir(self, d, sr): sr('200 OK', [('Content-Type', 'text/html')]) context = { 'directory': d.relpath(self.config.output), 'links': [f.relpath(d) for f in d.listdir()] } return (self.config.jinja.get_template('list_dir.tplt') .stream(**context)) def __call__(self, env, start_response): path = self.config.output + env.get('PATH_INFO') path_index = path + 'index.html' if not path.exists(): return self._not_found(start_response) if path.isfile(): return self._serve_file(path, start_response) if path_index.exists(): return self._serve_file(path_index, start_response) return self._list_dir(path, start_response) pass_config = click.make_pass_decorator(Config, ensure=True) @click.group() def cli(): pass @cli.command() @pass_config def clean(config): '''Clean the "output" directory''' click.echo('Clean output directory') for dir in config.output.dirs(): dir.rmtree() for file in config.output.files(): file.remove() @cli.command() @pass_config def build(config): '''Build the pelican static site''' subprocess.call(['pelican', '-s', config.settings]) @cli.command() @click.pass_context def rebuild(ctx): '''Run clean then build''' ctx.invoke(clean) ctx.invoke(build) @cli.command() @click.option('--no-debug', is_flag=True, help='remove the debug output') @click.option('--no-lr', is_flag=True, help='remove the livereload support') @click.argument('port', default=8000, required=False) @pass_config @click.pass_context def serve(ctx, config, no_debug, no_lr, port): '''Serve the 'output' directory, default port is 8000''' if port < 1024 or port > 65535: error_msg = 'port must be an integer between 1024 and 65535' raise click.BadParameter(error_msg, param_hint='port') debug = not no_debug inner_build = lambda: ctx.invoke(build) inner_build() if no_lr: with config.output: Handler = SimpleHTTPServer.SimpleHTTPRequestHandler httpd = SocketServer.TCPServer(('', port), Handler) click.echo('serving at port %d' % port) httpd.serve_forever() else: server = livereload.Server(Application(config)) server.watch(config.content, inner_build) server.watch(config.theme, inner_build) server.serve(port=port, debug=debug) @cli.command() @click.argument('title') @pass_config def new_post(config, title): '''Create a new blog entry''' date = datetime.date.today().isoformat() filename = '.'.join([date, utils.slugify(title), 'md']) filename = config.blog / filename click.echo('Create new post: %s' % click.style(filename, fg='green')) (config.jinja.get_template('new_post.tplt') .stream(title=title) .dump(filename, 'utf8')) @cli.command() @pass_config @click.pass_context def publish(ctx, config): ctx.invoke(build) subprocess.call(['ghp-import', config.output]) subprocess.call(['git', 'push', 'site', '-f', 'gh-pages:master']) if __name__ == '__main__': cli()