Browse Source

Replace fabric with click

Better handling of command line
No dependency on third part C libraries (paramiko)
Maxime Vidori 9 years ago
parent
commit
e0332cc3e8
3 changed files with 166 additions and 148 deletions
  1. 0 147
      fabfile.py
  2. 165 0
      gitoyen.py
  3. 1 1
      requirements.txt

+ 0 - 147
fabfile.py

@@ -1,147 +0,0 @@
-# coding: utf8
-from __future__ import unicode_literals
-
-import livereload
-import fabric.colors as colors
-import fabric.contrib.project as project
-import fabric.contrib.files as files
-import fabric.api as fabric
-import jinja2
-import path
-import pelican.utils as utils
-
-import collections
-import datetime
-import logging
-import logging.handlers
-import mimetypes
-import sys
-
-# Local path configuration (can be absolute or relative to fabfile)
-fabric.env.deploy_path = path.path('output')
-fabric.env.content_path = path.path('content')
-fabric.env.theme_path = path.path('../theme')
-
-fabric.env.jinja = jinja2.Environment(
-    loader=jinja2.PackageLoader('fabfile', 'templates')
-)
-
-MESSAGE_FORMAT = '%(levelname)s %(message)s'
-LEVELS = {
-    'WARNING': colors.yellow('WARN', bold=True),
-    'INFO': colors.blue('INFO', bold=True),
-    'DEBUG': colors.green('DEBUG', bold=True),
-    'CRITICAL': colors.magenta('CRIT', bold=True),
-    'ERROR': colors.red('ERROR', bold=True),
-}
-
-
-class FabricFormatter(logging.Formatter):
-    def format(self, record):
-        record.levelname = LEVELS.get(record.levelname) + ':'
-        return super(FabricFormatter, self).format(record)
-
-
-class Server(livereload.Server):
-    def _setup_logging(self):
-        super(Server, self)._setup_logging()
-        server_handler = logging.getLogger('livereload').handlers[0]
-        server_handler.setFormatter(FabricFormatter(MESSAGE_FORMAT))
-
-
-def application(env, start_response):
-    chunk_size = 64 * 1024
-
-    def not_found(sr):
-        sr('404 NOT FOUND', [('Content-Type', 'text/plain')])
-        return ['Not Found']
-
-    def serve_file(f, sr):
-        mime = mimetypes.guess_type(f)
-
-        sr('200 OK', [('Content-Type', mime[0])])
-        return f.chunks(chunk_size, 'rb')
-
-    def list_dir(d, sr):
-        sr('200 OK', [('Content-Type', 'text/html')])
-
-        context = {
-            'directory': d.relpath(fabric.env.deploy_path),
-            'links': [ f.relpath(d) for f in d.listdir() ]
-        }
-
-        return (fabric.env.jinja.get_template('list_dir.tplt')
-                .stream(**context))
-
-
-    path = fabric.env.deploy_path + env.get('PATH_INFO')
-    path_index = path + 'index.html'
-
-    if not path.exists():
-        return not_found(start_response)
-    if path.isfile():
-        return serve_file(path, start_response)
-    if path_index.exists():
-        return serve_file(path_index, start_response)
-
-    return list_dir(path, start_response)
-
-
-@fabric.task
-def clean():
-    if fabric.env.deploy_path.isdir():
-        fabric.local('rm -rf {deploy_path}'.format(**fabric.env))
-        fabric.local('mkdir {deploy_path}'.format(**fabric.env))
-
-@fabric.task
-def build():
-    fabric.local('pelican -s pelicanconf.py')
-
-@fabric.task
-def rebuild():
-    clean()
-    build()
-
-@fabric.task
-def regenerate():
-    fabric.local('pelican -r -s pelicanconf.py')
-
-@fabric.task
-def serve(*args):
-    port = args[0] if len(args) > 0 else 8000
-
-    if not isinstance(port, int) or port < 1024 or port > 65535:
-        print(colors.red('Port must be an integer between 1024 and 65535...'))
-        return
-
-    build()
-    server = Server(application)
-    server.watch(fabric.env.content_path, build)
-    server.watch(fabric.env.theme_path, build)
-    server.serve(port=port, debug=True)
-
-@fabric.task
-def reserve():
-    build()
-    serve()
-
-@fabric.task
-def new_post(*args):
-    title = args[0] if len(args) > 0 else fabric.prompt('New post title?')
-    title = unicode(title, 'utf8')
-
-    date = datetime.date.today().isoformat()
-    filename = '.'.join([date, utils.slugify(title), 'md'])
-    filename = fabric.env.content_path / filename
-    print(' '.join([LEVELS['INFO'], 'Create new post:', filename]))
-
-    (fabric.env.jinja.get_template('new_post.tplt')
-     .stream(title=title)
-     .dump(filename, 'utf8'))
-
-@fabric.task
-def publish():
-    build()
-    fabric.local('ghp-import {0}'.format(fabric.env.deploy_path))
-    fabric.local('git push origin -f gh-pages:master')
-

+ 165 - 0
gitoyen.py

@@ -0,0 +1,165 @@
+# 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[0])])
+        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()

+ 1 - 1
requirements.txt

@@ -1,7 +1,7 @@
 pelican
 markdown
 ghp-import
-fabric
+click
 path.py
 livereload
 beautifulsoup4