Skip to content

Commit

Permalink
Upgrade pyserve.
Browse files Browse the repository at this point in the history
  • Loading branch information
klen committed Jun 13, 2012
1 parent 5c26021 commit c96fc8f
Show file tree
Hide file tree
Showing 68 changed files with 554 additions and 11 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Expand Up @@ -2,3 +2,4 @@ include README.rst
include requirements.txt
include LICENSE
include Changelog
recursive-include pyserve/static *
5 changes: 4 additions & 1 deletion Makefile
@@ -1,7 +1,7 @@
PYTHON=$(CURDIR)/env/bin/python
PIP=$(CURDIR)/env/bin/pip

.PHONY: all clean update serve register upload
.PHONY: all clean update serve register upload test

all: env dist update

Expand All @@ -27,5 +27,8 @@ serve:
register:
$(PYTHON) setup.py register

test:
$(PYTHON) setup.py test

upload: env
$(PYTHON) $(CURDIR)/setup.py sdist register upload
16 changes: 13 additions & 3 deletions README.rst
Expand Up @@ -5,12 +5,20 @@ PyServe is the simple command interface for HTTP serving directories.

::
# Python 2.*
$ python -m SimpleHTTPServer

# Python 3
$ python

VS

::

# Python 2.*
$ serve

# Python 3
$ serve


Expand All @@ -21,8 +29,8 @@ VS

Requirements
============
- python > 2.6
- Flask 0.8
- python (2.6+ or 3.0+)
- Bottle 0.10+


Installation
Expand All @@ -42,7 +50,8 @@ Usage
::

$ serve --help
usage: serve [-h] [-p PORT] [-s] [path]

usage: serve [-h] [-p PORT] [-s] [-a] [path]

Serve current directory

Expand All @@ -53,6 +62,7 @@ Usage
-h, --help show this help message and exit
-p PORT, --port PORT The port of the webserver.
-s, --share Make server available externally.
-a, --autoindex Enable autoindex files.


.. _Distribute: http://pypi.python.org/pypi/distribute
Expand Down
5 changes: 5 additions & 0 deletions pyserve/__init__.py
Expand Up @@ -3,3 +3,8 @@
__email__ = "horneds@gmail.com"
__license__ = "GNU LGPL"
__url__ = 'http://github.com/klen/pyserve'


if __name__ == '__main__':
from .main import main
main()
35 changes: 35 additions & 0 deletions pyserve/app.py
@@ -0,0 +1,35 @@
" Define web application. "

import bottle


APP = bottle.Bottle()


@APP.route('/__serve_static__/<path:re:.*>')
def static(path):
return bottle.static_file(path, APP.config.static)


@APP.route('/')
@APP.route('/<path:re:.*>')
def index(path=''):
try:
entry = APP.config.root.parse(path)

if entry.is_file():
return bottle.static_file(path, APP.config.root.abspath)

if APP.config.autoindex:
index = [e for e in entry.entries if e.name in ['index.html', 'index.htm']]
if index:
return bottle.static_file(index[0].path, APP.config.root.abspath)

return bottle.template('_pyserve_.html',
template_lookup=[APP.config.static],
dir=entry,
app=APP)

except IOError, e:
return e
bottle.redirect('/')
165 changes: 165 additions & 0 deletions pyserve/core.py
@@ -0,0 +1,165 @@
from datetime import datetime
from os import path as op, listdir

from icons import ICONS_BY_EXT, ICONS_BY_NAME


def compare(e1, e2):
if type(e1) is not type(e2):
return 1 if type(e1) is File else -1
return cmp(e1.name, e2.name)


class Entry(object):

default_icon = 'cross.png'

def __init__(self, path, root=None):
self.root = root
self.path = path
self.abspath = root.join(path)
self.name = op.basename(self.abspath)
self.hidden = self.name.startswith('.')

def __new__(self, path, root=None):
if not root or not path or path == '/':
return RootDirectory.__new__(RootDirectory, path)

else:

abspath = root.join(path)
if op.isdir(abspath):
return Directory.__new__(Directory, path, root)

elif op.isfile(abspath):
return File.__new__(File, path, root)

else:
raise IOError('{0} does not exists.'.format(abspath))

def join(self, path):
path = path.strip('/')
return op.join(self.abspath, path) if path else self.abspath

def parse(self, path):
abspath = op.abspath(self.join(path))
if self.abspath == abspath:
return self
return Entry(path, root=self)

@staticmethod
def is_file():
return False

@property
def link(self):
if not self.path.startswith('/'):
return "/" + self.path
return self.path

@property
def icon(self):
return self.default_icon

@property
def modified(self):
return datetime.fromtimestamp(op.getmtime(self.abspath))

@property
def size(self):
return op.getsize(self.abspath)

def __str__(self):
return "<%s [%s]>" % (self.__class__.__name__, self.abspath)

__repr__ = __str__


class Directory(Entry):
default_icon = 'folder.png'

def __new__(self, *args, **kwargs):
return object.__new__(Directory, *args, **kwargs)

@property
def breadcrumb(self):
paths = [''] + self.abspath[len(self.root.abspath):].split('/')
paths.pop()
breadcrumb = []
while paths:
path = op.join(*paths)
paths.pop()
breadcrumb.append(Entry(path, root=self.root))
return reversed(list(enumerate(breadcrumb)))

def is_root(self):
return self == self.root

@property
def entries(self):
entries = []

for path in listdir(self.abspath):
entries.append(Entry(op.join(self.path, path), self.root))
entries = sorted(entries, cmp=compare)

if not self.is_root():
entries.insert(0, ParentDirectory(self.path, self.root))

return entries


class File(Entry):
default_icon = 'file.png'

def __new__(self, *args, **kwargs):
return object.__new__(File, *args, **kwargs)

def __init__(self, *args, **kwargs):
super(File, self).__init__(*args, **kwargs)
self.ext = op.splitext(self.name)[1].strip('.')

@staticmethod
def is_file():
return True

@property
def icon(self):
return ICONS_BY_NAME.get(
self.name,
ICONS_BY_EXT.get(
self.ext,
self.default_icon
))


class RootDirectory(Directory):
default_icon = 'root.png'

def __init__(self, path, root=None):
self.abspath = op.abspath(path)
super(RootDirectory, self).__init__('/', self)
self.name = 'root'

def __new__(self, *args, **kwargs):
return object.__new__(RootDirectory, *args, **kwargs)

@property
def breadcrumb(self):
return []

@property
def link(self):
return self.path


class ParentDirectory(Directory):
default_icon = 'parent.png'

def __init__(self, path, root=None):
path = op.dirname(path)
super(ParentDirectory, self).__init__(path, root=root)
self.name = '..'

def __new__(self, *args, **kwargs):
return object.__new__(ParentDirectory, *args, **kwargs)
65 changes: 65 additions & 0 deletions pyserve/icons.py
@@ -0,0 +1,65 @@
by_ext = [
('py.png', 'py'),
('python.png', 'pyc'),
('page_white_text_width.png', ['md', 'markdown', 'rst', 'rtf']),
('page_white_text.png', 'txt'),
('page_white_code.png', ['html', 'htm', 'cgi']),
('page_white_visualstudio.png', ['asp', 'vb']),
('page_white_ruby.png', 'rb'),
('page_code.png', 'xhtml'),
('page_white_code_red.png', ['xml', 'xsl', 'xslt', 'yml']),
('script.png', ['js', 'json', 'applescript', 'htc']),
('layout.png', ['css', 'less']),
('page_white_php.png', 'php'),
('page_white_c.png', 'c'),
('page_white_cplusplus.png', 'cpp'),
('page_white_h.png', 'h'),
('database.png', ['db', 'sqlite', 'sqlite3']),
('page_white_database.png', 'sql'),
('page_white_gear.png', ['conf', 'cfg', 'ini', 'reg', 'sys']),
('page_white_zip.png', ['zip', 'tar', 'gz', 'tgz', '7z', 'alz', 'rar', \
'bin', 'cab']),
('cup.png', 'jar'),
('page_white_cup.png', ['java', 'jsp']),
('application_osx_terminal.png', 'sh'),
('page_white_acrobat.png', 'pdf'),
('package.png', ['pkg', 'dmg']),
('shape_group.png', ['ai', 'svg', 'eps']),
('application_osx.png', 'app'),
('cursor.png', 'cur'),
('feed.png', 'rss'),
('cd.png', ['iso', 'vcd', 'toast']),
('page_white_powerpoint.png', ['ppt', 'pptx']),
('page_white_excel.png', ['xls', 'xlsx', 'csv']),
('page_white_word.png', ['doc', 'docx']),
('page_white_flash.png', 'swf'),
('page_white_actionscript.png', ['fla', 'as']),
('comment.png', 'smi'),
('disk.png', ['bak', 'bup']),
('application_xp_terminal.png', ['bat', 'com']),
('application.png', 'exe'),
('key.png', 'cer'),
('cog.png', ['dll', 'so']),
('pictures.png', 'ics'),
('picture.png', ['gif', 'png', 'jpg', 'jpeg', 'bmp', 'ico']),
('film.png', ['avi', 'mkv']),
('error.png', 'log'),
('music.png', ['mpa', 'mp3', 'off', 'wav']),
('font.png', ['ttf', 'eot']),
('vcard.png', 'vcf')
]

ICONS_BY_NAME = dict(
Makefile='page_white_gear.png',
Rakefile='page_white_gear.png',
README='page_white_text_width.png',
LICENSE='shield.png',

)

ICONS_BY_EXT = dict()
for icon, exts in by_ext:
if not isinstance(exts, list):
exts = [exts]
for e in exts:
ICONS_BY_EXT[e] = icon
28 changes: 21 additions & 7 deletions pyserve/main.py
@@ -1,14 +1,25 @@
from os import path as op
from sys import stderr

from argparse import ArgumentParser
from flask import Flask
from flaskext.autoindex import AutoIndex
import bottle

from .app import APP
from .core import Entry

def serve(path, port=5000, share=False):
app = Flask(__name__)
AutoIndex(app, browse_root=path)
app.run(port=int(port), host='127.0.0.1' if not share else '0.0.0.0')

MODULE_ROOT = op.abspath(op.dirname(__file__))


def serve(path, port=5000, share=False, autoindex=False):
APP.config.root = Entry(path)
APP.config.static = op.join(MODULE_ROOT, 'static')
APP.config.port = port
APP.config.host = '127.0.0.1' if not share else '0.0.0.0'
APP.config.autoindex = autoindex
stderr.write(str(APP.config) + '\n')
bottle.TEMPLATE_PATH = [APP.config.static]
bottle.run(APP, port=int(port), host=APP.config.host)


def main():
Expand All @@ -23,5 +34,8 @@ def main():
parser.add_argument('-s', '--share',
action='store_true',
help='Make server available externally.')
parser.add_argument('-a', '--autoindex',
action='store_true',
help='Enable autoindex files.')
args = parser.parse_args()
serve(args.path, port=args.port, share=args.share)
serve(args.path, port=args.port, share=args.share, autoindex=args.autoindex)

0 comments on commit c96fc8f

Please sign in to comment.