Codebase list flask-restful / 94d72b4
Update tracked files Jonathan Carter 6 years ago
111 changed file(s) with 0 addition(s) and 1272 deletion(s). Raw diff Collapse all Expand all
+0
-276
flask_autoindex/__init__.py less more
0 # -*- coding: utf-8 -*-
1 """
2 flask_autoindex
3 ~~~~~~~~~~~~~~~
4
5 The mod_autoindex for `Flask <http://flask.pocoo.org/>`_.
6
7 :copyright: (c) 2010-2013 by Heungsub Lee.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import absolute_import
11 from future.builtins import str
12 from future.builtins import object
13 import os
14 import re
15
16 from flask import *
17 from flask_silk import Silk
18 from jinja2 import FileSystemLoader, TemplateNotFound
19 from werkzeug import cached_property
20
21 from .entry import *
22 from . import icons
23
24
25 __version__ = '0.5'
26 __autoindex__ = '__autoindex__'
27
28
29 class AutoIndex(object):
30 """This class makes the Flask application to serve automatically
31 generated index page. The wrapped application will route ``/`` and
32 ``/<path:path>`` when ``add_url_rules`` is ``True``. Here's a simple
33 example::
34
35 app = Flask(__name__)
36 AutoIndex(app, '/home/someone/public_html', add_url_rules=True)
37
38 :param base: a Flask application.
39 :param browse_root: a path which is served by root address. By default,
40 this is the working directory, but you can set it to
41 fix your app to always use one location if you like.
42 :param add_url_rules: if it is ``True``, the wrapped application routes
43 ``/`` and ``/<path:path>`` to autoindex. default
44 is ``True``.
45 :param template_context: would be passed to the Jinja2 template when
46 rendering an AutoIndex page.
47 :param silk_options: keyword options for :class:`flask_silk.Silk`.
48 """
49
50 shared = None
51
52 def _register_shared_autoindex(self, state=None, app=None):
53 """Registers a magic module named __autoindex__."""
54 app = app or state.app
55 if __autoindex__ not in app.blueprints:
56 static_folder = os.path.join(__path__[0], 'static')
57 template_folder = os.path.join(__path__[0], 'templates')
58 shared = Blueprint(__autoindex__, __name__,
59 template_folder=template_folder)
60 @shared.route('/__autoindex__/<path:filename>')
61 def static(filename):
62 return send_from_directory(static_folder, filename)
63 app.register_blueprint(shared)
64
65 def __new__(cls, base, *args, **kwargs):
66 if isinstance(base, Flask):
67 return object.__new__(AutoIndexApplication)
68 elif isinstance(base, Blueprint):
69 return object.__new__(AutoIndexBlueprint)
70 else:
71 raise TypeError("'base' should be Flask or Blueprint.")
72
73 def __init__(self, base, browse_root=None, add_url_rules=True,
74 template_context=None, silk_options=None,
75 show_hidden=False):
76 """Initializes an autoindex instance."""
77 self.base = base
78 if browse_root:
79 browse_root = str(browse_root)
80 else:
81 browse_root = os.path.curdir
82 self.rootdir = RootDirectory(browse_root, autoindex=self)
83 self.template_context = template_context
84 if silk_options is None:
85 silk_options = {}
86 silk_options['silk_path'] = silk_options.get('silk_path', '/__icons__')
87 self.silk = Silk(self.base, **silk_options)
88 self.show_hidden = show_hidden
89 self.icon_map = []
90 self.converter_map = []
91 if add_url_rules:
92 @self.base.route('/')
93 @self.base.route('/<path:path>')
94 def autoindex(path='.'):
95 return self.render_autoindex(path)
96
97 def render_autoindex(self, path, browse_root=None, template=None,
98 template_context=None, endpoint='.autoindex',
99 show_hidden=None, sort_by='name',
100 mimetype=None):
101 """Renders an autoindex with the given path.
102
103 :param path: the relative path.
104 :param browse_root: if it is specified, it used to a path which is
105 served by root address.
106 :param template: the template name.
107 :param template_context: would be passed to the Jinja2 template when
108 rendering an AutoIndex page.
109 :param endpoint: an endpoint which is a function.
110 :param show_hidden: whether to show hidden files (starting with '.')
111 :param sort_by: the property to sort the entrys by.
112 :param mimetype: set static mime type for files (no auto detection).
113 """
114 if browse_root:
115 rootdir = RootDirectory(browse_root, autoindex=self)
116 else:
117 rootdir = self.rootdir
118 path = re.sub(r'\/*$', '', path)
119 abspath = os.path.join(rootdir.abspath, path)
120 if os.path.isdir(abspath):
121 sort_by = request.args.get('sort_by', sort_by)
122 order = {'asc': 1, 'desc': -1}[request.args.get('order', 'asc')]
123 curdir = Directory(path, rootdir)
124 if show_hidden == None: show_hidden = self.show_hidden
125 entries = curdir.explore(sort_by=sort_by, order=order,
126 show_hidden=show_hidden)
127 if callable(endpoint):
128 endpoint = endpoint.__name__
129 context = {}
130 if template_context is not None:
131 context.update(template_context)
132 if self.template_context is not None:
133 context.update(self.template_context)
134 context.update(
135 curdir=curdir, entries=entries,
136 sort_by=sort_by, order=order, endpoint=endpoint)
137 if template:
138 return render_template(template, **context)
139 try:
140 template = '{0}autoindex.html'.format(self.template_prefix)
141 return render_template(template, **context)
142 except TemplateNotFound as e:
143 template = '{0}/autoindex.html'.format(__autoindex__)
144 return render_template(template, **context)
145 elif os.path.isfile(abspath):
146 if mimetype:
147 return send_file(abspath, mimetype=mimetype)
148 else:
149 return send_file(abspath)
150 else:
151 return abort(404)
152
153 def add_icon_rule(self, icon, rule=None, ext=None, mimetype=None,
154 name=None, filename=None, dirname=None, cls=None):
155 """Adds a new icon rule.
156
157 There are many shortcuts for rule. You can use one or more shortcuts in
158 a rule.
159
160 `rule`
161 A function which returns ``True`` or ``False``. It has one argument
162 which is an instance of :class:`Entry`. Example usage::
163
164 def has_long_name(ent):
165 return len(ent.name) > 10
166 idx.add_icon_rule('brick.png', rule=has_log_name)
167
168 Now the application represents files or directorys such as
169 ``very-very-long-name.js`` with ``brick.png`` icon.
170
171 `ext`
172 A file extension or file extensions to match with a file::
173
174 idx.add_icon_rule('ruby.png', ext='ruby')
175 idx.add_icon_rule('bug.png', ext=['bug', 'insect'])
176
177 `mimetype`
178 A mimetype or mimetypes to match with a file::
179
180 idx.add_icon_rule('application.png', mimetype='application/*')
181 idx.add_icon_rule('world.png', mimetype=['image/icon', 'x/*'])
182
183 `name`
184 A name or names to match with a file or directory::
185
186 idx.add_icon_rule('error.png', name='error')
187 idx.add_icon_rule('database.png', name=['mysql', 'sqlite'])
188
189 `filename`
190 Same as `name`, but it matches only a file.
191
192 `dirname`
193 Same as `name`, but it matches only a directory.
194
195 If ``icon`` is callable, it is used to ``rule`` function and the result
196 is used to the url for an icon. This way is useful for getting an icon
197 url dynamically. Here's a nice example::
198
199 def get_favicon(ent):
200 favicon = 'favicon.ico'
201 if type(ent) is Directory and favicon in ent:
202 return '/' + os.path.join(ent.path, favicon)
203 return False
204 idx.add_icon_rule(get_favicon)
205
206 Now a directory which has a ``favicon.ico`` guesses the ``favicon.ico``
207 instead of silk's ``folder.png``.
208 """
209 if name:
210 filename = name
211 directoryname = name
212 call = lambda m, *args: m.__func__(self, *args)
213 if ext:
214 call(File.add_icon_rule_by_ext, icon, ext)
215 if mimetype:
216 call(File.add_icon_rule_by_mimetype, icon, mimetype)
217 if filename:
218 call(File.add_icon_rule_by_name, icon, filename)
219 if dirname:
220 call(Directory.add_icon_rule_by_name, icon, dirname)
221 if cls:
222 call(Entry.add_icon_rule_by_class, icon, cls)
223 if callable(rule) or callable(icon):
224 call(Entry.add_icon_rule, icon, rule)
225
226 @property
227 def template_prefix(self):
228 raise NotImplementedError()
229
230
231 class AutoIndexApplication(AutoIndex):
232 """An AutoIndex which supports flask applications."""
233
234 template_prefix = ''
235
236 def __init__(self, app, browse_root=None, **silk_options):
237 super(AutoIndexApplication, self).__init__(app, browse_root,
238 **silk_options)
239 self.app = app
240 self._register_shared_autoindex(app=self.app)
241
242
243 class AutoIndexBlueprint(AutoIndex):
244 """An AutoIndex which supports flask blueprints.
245
246 .. versionadded:: 0.3.1
247 """
248
249 def __init__(self, blueprint, browse_root=None, **silk_options):
250 super(AutoIndexBlueprint, self).__init__(blueprint, browse_root,
251 **silk_options)
252 self.blueprint = self.base
253 self.blueprint.record_once(self._register_shared_autoindex)
254
255 @cached_property
256 def template_prefix(self):
257 return self.blueprint.name + '/'
258
259
260 class AutoIndexModule(AutoIndexBlueprint):
261 """Deprecated module support.
262
263 .. versionchanged:: 0.3.1
264 ``AutoIndexModule`` was deprecated. Use ``AutoIndexBlueprint`` instead.
265 """
266
267 def __init__(self, *args, **kwargs):
268 import warnings
269 warnings.warn('AutoIndexModule is deprecated; ' \
270 'use AutoIndexBlueprint instead.', DeprecationWarning)
271 super(AutoIndexModule, self).__init__(*args, **kwargs)
272
273 @property
274 def mod(self):
275 return self.blueprint
+0
-332
flask_autoindex/entry.py less more
0 from past.builtins import cmp
1 from future import standard_library
2 standard_library.install_hooks()
3 # -*- coding: utf-8 -*-
4 from datetime import datetime
5 from fnmatch import fnmatch
6 from mimetypes import guess_type
7 import functools
8 import os
9 import re
10 from future.utils import with_metaclass
11 from future.moves.urllib.parse import urljoin
12 from flask import url_for, send_file
13 from werkzeug import cached_property
14
15
16 Default = None
17
18
19 is_same_path = lambda x, y: os.stat(x) == os.stat(y)
20
21
22 def _make_mimetype_matcher(mimetype):
23 return lambda ent: fnmatch(guess_type(ent.name)[0] or '', mimetype)
24
25
26 def _make_args_for_entry(args, kwargs):
27 if not args:
28 raise TypeError('path is required, but not given')
29 rootdir = autoindex = None
30 args = list(args)
31 try:
32 path = kwargs.get('path', args.pop(0))
33 rootdir = kwargs.get('rootdir', args.pop(0))
34 autoindex = kwargs.get('autoindex', args.pop(0))
35 except IndexError:
36 pass
37 return (path, rootdir, autoindex)
38
39
40 class _EntryMeta(type):
41 """The meta class for :class:`Entry`."""
42
43 def __call__(cls, *args, **kwargs):
44 """If an instance already initialized, just returns."""
45 ent = cls.__new__(cls, *args, **kwargs)
46 try:
47 ent.path
48 except AttributeError:
49 ent.__init__(*args, **kwargs)
50 return ent
51
52
53 class Entry(with_metaclass(_EntryMeta, object)):
54 """This class wraps file or directory. It is an abstract class, but it
55 returns a derived instance. You can make an instance such as::
56
57 directory = Entry('/home/someone/public_html')
58 assert isinstance(foler, Directory)
59 file = Entry('/home/someone/public_html/favicon.ico')
60 assert isinstance(file, File)
61 """
62
63 HIDDEN = re.compile('^\.')
64
65 def __new__(cls, *args, **kwargs):
66 """Returns a file or directory instance."""
67 path, rootdir, autoindex = _make_args_for_entry(args, kwargs)
68 if rootdir:
69 abspath = os.path.join(rootdir.abspath, path)
70 else:
71 abspath = os.path.abspath(path)
72 if os.path.isdir(abspath):
73 return Directory.__new__(Directory, path, rootdir, autoindex)
74 elif os.path.isfile(abspath):
75 return File.__new__(File, path, rootdir, autoindex)
76 else:
77 raise IOError('{0} does not exists.'.format(abspath))
78
79 def __init__(self, path, rootdir=None, autoindex=None):
80 """Initializes an entry instance."""
81 self.rootdir = rootdir
82 self.autoindex = autoindex
83 try:
84 rootpath = self.rootdir.abspath
85 if not autoindex and self.rootdir:
86 self.autoindex = self.rootdir.autoindex
87 except AttributeError:
88 rootpath = ''
89 self.path = path
90 self.abspath = os.path.join(rootpath, self.path)
91 self.name = os.path.basename(self.abspath)
92 self.hidden = bool(self.HIDDEN.match(self.name))
93 if self.rootdir:
94 self.rootdir._register_descendant(self)
95
96 def is_root(self):
97 """Returns ``True`` if it is a root directory."""
98 return isinstance(self, RootDirectory)
99
100 @property
101 def parent(self):
102 if self.is_root():
103 return None
104 elif is_same_path(os.path.dirname(self.abspath), self.rootdir.abspath):
105 return self.rootdir
106 return Entry(os.path.dirname(self.path), self.rootdir)
107
108 @property
109 def modified(self):
110 """Returns modified time of this."""
111 return datetime.fromtimestamp(os.path.getmtime(self.abspath))
112
113 @classmethod
114 def add_icon_rule(cls, icon, rule=None):
115 """Adds a new icon rule globally."""
116 cls.icon_map.append((icon, rule))
117
118 @classmethod
119 def add_icon_rule_by_name(cls, icon, name):
120 """Adds a new icon rule by the name globally."""
121 cls.add_icon_rule(icon, lambda ent: ent.name == name)
122
123 @classmethod
124 def add_icon_rule_by_class(cls, icon, _class):
125 """Adds a new icon rule by the class globally."""
126 cls.add_icon_rule(icon, lambda ent: isinstance(ent, _class))
127
128 def guess_icon(self):
129 """Guesses an icon from itself."""
130 def get_icon_url():
131 try:
132 if self.autoindex:
133 icon_map = self.autoindex.icon_map + self.icon_map
134 else:
135 icon_map = self.icon_map
136 for icon, rule in icon_map:
137 if not rule and callable(icon):
138 matched = icon = icon(self)
139 else:
140 matched = rule(self)
141 if matched:
142 return icon
143 except AttributeError:
144 pass
145 try:
146 return self.default_icon
147 except AttributeError:
148 raise GuessError('There is no matched icon.')
149 try:
150 return urljoin(url_for('.silkicon', filename=''), get_icon_url())
151 except (AttributeError, RuntimeError):
152 return 'ERROR'
153 return get_icon_url()
154
155
156 class File(Entry):
157 """This class wraps a file."""
158
159 EXTENSION = re.compile('\.([^.]+)$')
160
161 default_icon = 'page_white.png'
162 icon_map = []
163
164 def __new__(cls, path, rootdir=None, autoindex=None):
165 try:
166 return rootdir._descendants[(path, autoindex)]
167 except (AttributeError, KeyError):
168 pass
169 return object.__new__(cls)
170
171 def __init__(self, path, rootdir=None, autoindex=None):
172 super(File, self).__init__(path, rootdir, autoindex)
173 try:
174 self.ext = re.search(self.EXTENSION, self.name).group(1)
175 except AttributeError:
176 self.ext = None
177
178 @cached_property
179 def data(self):
180 """Data of this file."""
181 with open(self.abspath) as f:
182 return ''.join(f.readlines())
183
184 @cached_property
185 def mimetype(self):
186 """A mimetype of this file."""
187 return guess_type(self.abspath)
188
189 @cached_property
190 def size(self):
191 """A size of this file."""
192 return os.path.getsize(self.abspath)
193
194 @classmethod
195 def add_icon_rule_by_ext(cls, icon, ext):
196 """Adds a new icon rule by the file extension globally."""
197 cls.add_icon_rule(icon, lambda ent: ent.ext == ext)
198
199 @classmethod
200 def add_icon_rule_by_mimetype(cls, icon, mimetype):
201 """Adds a new icon rule by the mimetype globally."""
202 cls.add_icon_rule(icon, _make_mimetype_matcher(mimetype))
203
204
205 class Directory(Entry):
206 """This class wraps a directory."""
207
208 default_icon = 'folder.png'
209 icon_map = []
210
211 def __new__(cls, *args, **kwargs):
212 """If the path is same with root path, it returns a
213 :class:`RootDirectory` object.
214 """
215 path, rootdir, autoindex = _make_args_for_entry(args, kwargs)
216 if not rootdir:
217 return RootDirectory(path, autoindex)
218 try:
219 return rootdir._descendants[(path, autoindex)]
220 except KeyError:
221 pass
222 rootpath = rootdir.abspath
223 if is_same_path(os.path.join(rootpath, path), rootpath):
224 if not rootdir:
225 rootdir = RootDirectory(rootpath, autoindex)
226 return rootdir
227 return object.__new__(cls)
228
229 def explore(self, sort_by='name', order=1, show_hidden=False):
230 """It is a generator. Each item is a child entry."""
231
232 def compare(ent1, ent2):
233 def asc():
234 if sort_by != 'modified' and type(ent1) is not type(ent2):
235 return 1 if type(ent1) is File else -1
236 else:
237 try:
238 return cmp(getattr(ent1, sort_by),
239 getattr(ent2, sort_by))
240 except AttributeError:
241 return cmp(getattr(ent1, 'name'),
242 getattr(ent2, 'name'))
243 return asc() * order
244 if not self.is_root():
245 yield _ParentDirectory(self)
246 rootdir = self.rootdir
247 else:
248 rootdir = self
249 dirlist = os.listdir(self.abspath)
250 entries = []
251 for name in dirlist:
252 try:
253 entries.append(Entry(os.path.join(self.path, name), rootdir))
254 except IOError:
255 continue # ignore stuff like broken links
256 entries = sorted(entries, key=functools.cmp_to_key(compare))
257 for ent in entries:
258 if show_hidden or not ent.hidden:
259 yield ent
260
261 def get_child(self, childname):
262 """Returns a child file or directory."""
263 if childname in self:
264 if self.path != '.':
265 path = os.path.join(self.path, childname)
266 else:
267 path = childname
268 return Entry(path, self.rootdir)
269 else:
270 raise IOError('{0} does not exist'.format(childname))
271
272 def __contains__(self, path_or_entry):
273 """Checks this directory has a file or directory.
274
275 public_html = Directory('public_html')
276 'favicon.ico' in public_html
277 File('favicon.ico', public_html) in public_html
278 """
279 if isinstance(path_or_entry, Entry):
280 path = os.path.relpath(path_or_entry.path, self.path)
281 if os.path.pardir in path:
282 return False
283 else:
284 path = path_or_entry
285 return os.path.exists(os.path.join(self.abspath, path))
286
287
288 class RootDirectory(Directory):
289 """This class wraps a root directory."""
290
291 default_icon = 'server.png'
292 icon_map = []
293 _rootdirs = {}
294
295 def __new__(cls, path, autoindex=None):
296 try:
297 return RootDirectory._rootdirs[(path, autoindex)]
298 except KeyError:
299 return object.__new__(cls)
300
301 def __init__(self, path, autoindex=None):
302 super(RootDirectory, self).__init__('.', autoindex=autoindex)
303 self.abspath = os.path.abspath(path)
304 self.rootdir = self
305 self._descendants = {}
306 RootDirectory._register_rootdir(self)
307
308 @classmethod
309 def _register_rootdir(cls, rootdir):
310 cls._rootdirs[(rootdir.abspath, rootdir.autoindex)] = rootdir
311
312 def _register_descendant(self, entry):
313 self._descendants[(entry.path, entry.autoindex)] = entry
314
315
316 class _ParentDirectory(Directory):
317 """This class wraps a parent directory."""
318
319 default_icon = 'arrow_turn_up.png'
320 icon_map = []
321
322 def __new__(cls, *args, **kwargs):
323 return object.__new__(cls)
324
325 def __init__(self, child_directory):
326 path = os.path.join(child_directory.path, '..')
327 super(_ParentDirectory, self).__init__(path, child_directory.rootdir)
328
329
330 class GuessError(RuntimeError): pass
331 class MarkupError(RuntimeError): pass
+0
-81
flask_autoindex/icons.py less more
0 # -*- coding: utf-8 -*-
1 from __future__ import absolute_import
2
3 from .entry import File, Default
4
5
6 by_extension = [
7 ('page_white_python.png', 'py'),
8 ('python.png', 'pyc'),
9 ('page_white_text_width.png', ['md', 'markdown', 'rst', 'rtf']),
10 ('page_white_code.png', ['html', 'htm', 'cgi']),
11 ('page_white_visualstudio.png', ['asp', 'vb']),
12 ('page_white_ruby.png', 'rb'),
13 ('page_code.png', 'xhtml'),
14 ('page_white_code_red.png', ['xml', 'xsl', 'xslt', 'yml']),
15 ('script.png', ['js', 'json', 'applescript', 'htc']),
16 ('layout.png', ['css', 'less']),
17 ('page_white_php.png', 'php'),
18 ('page_white_c.png', 'c'),
19 ('page_white_cplusplus.png', 'cpp'),
20 ('page_white_h.png', 'h'),
21 ('database.png', ['db', 'sqlite', 'sqlite3']),
22 ('page_white_database.png', 'sql'),
23 ('page_white_gear.png', ['conf', 'cfg', 'ini', 'reg', 'sys']),
24 ('page_white_zip.png', ['zip', 'tar', 'gz', 'tgz', '7z', 'alz', 'rar', \
25 'bin', 'cab']),
26 ('cup.png', 'jar'),
27 ('page_white_cup.png', ['java', 'jsp']),
28 ('application_osx_terminal.png', 'sh'),
29 ('page_white_acrobat.png', 'pdf'),
30 ('package.png', ['pkg', 'dmg']),
31 ('shape_group.png', ['ai', 'svg', 'eps']),
32 ('application_osx.png', 'app'),
33 ('cursor.png', 'cur'),
34 ('feed.png', 'rss'),
35 ('cd.png', ['iso', 'vcd', 'toast']),
36 ('page_white_powerpoint.png', ['ppt', 'pptx']),
37 ('page_white_excel.png', ['xls', 'xlsx', 'csv']),
38 ('page_white_word.png', ['doc', 'docx']),
39 ('page_white_flash.png', 'swf'),
40 ('page_white_actionscript.png', ['fla', 'as']),
41 ('comment.png', 'smi'),
42 ('disk.png', ['bak', 'bup']),
43 ('application_xp_terminal.png', ['bat', 'com']),
44 ('application.png', 'exe'),
45 ('key.png', 'cer'),
46 ('cog.png', ['dll', 'so']),
47 ('pictures.png', 'ics'),
48 ('error.png', 'log'),
49 ('music.png', 'mpa'),
50 ('font.png', ['ttf', 'eot']),
51 ('vcard.png', 'vcf'),
52 ('page_white.png', Default)
53 ]
54 by_filename = [
55 ('page_white_gear.png', ['Makefile', 'Rakefile'])
56 ]
57 by_mimetype = [
58 ('page_white_text.png', 'text/*'),
59 ('picture.png', 'image/*'),
60 ('music.png', 'audio/*'),
61 ('film.png', 'video/*')
62 ]
63
64
65 def to_list(val):
66 if not isinstance(val, list):
67 return [val]
68 else:
69 return val
70
71
72 for icon, exts in by_extension:
73 for ext in to_list(exts):
74 File.add_icon_rule_by_ext(icon, ext)
75 for icon, filenames in by_filename:
76 for name in to_list(filenames):
77 File.add_icon_rule_by_name(icon, name)
78 for icon, mimetypes in by_mimetype:
79 for mimetype in to_list(mimetypes):
80 File.add_icon_rule_by_mimetype(icon, mimetype)
+0
-10
flask_autoindex/run.py less more
0 import os.path
1 from flask import Flask
2 from flask_autoindex import AutoIndex
3
4
5 app = Flask(__name__)
6 AutoIndex(app)
7
8 if __name__ == '__main__':
9 app.run()
flask_autoindex/static/asc.gif less more
Binary diff not shown
+0
-171
flask_autoindex/static/autoindex.css less more
0 * {
1 margin: 0;
2 padding: 0;
3 border: none;
4 }
5
6 body, table {
7 font-family: Helvetica, Arial, sans-serif;
8 font-size: 14px;
9 }
10
11 a:link, a:visited {
12 text-decoration: none;
13 }
14 a:link {
15 color: #36c;
16 }
17 a:visited {
18 color: #333;
19 }
20 a:hover {
21 text-decoration: underline;
22 }
23
24 #readme {
25 padding: 10px 26px;
26 border-bottom: 1px solid #eee;
27 font-size: 12px;
28 color: #333;
29 background: #fafafa;
30 }
31 #readme pre {
32 line-height: 1.2;
33 }
34 #readme p, #readme ul, #readme ol,
35 #readme h1, #readme h2, #readme h3, #readme h4, #readme h5, #readme h6 {
36 display: block;
37 margin-bottom: 1em;
38 }
39 #readme h1, #readme h2, #readme h3, #readme h4, #readme h5, #readme h6 {
40 padding: 0;
41 color: #456;
42 text-align: left;
43 font-family: sans-serif;
44 font-weight: bolder;
45 }
46 #readme pre code {
47 display: block;
48 padding: 9px;
49 margin: 0 -10px 1em;
50 border: 1px solid #cde;
51 color: #567;
52 background: #fff;
53 }
54 #readme li {
55 margin-left: 2em;
56 }
57 #readme blockquote {
58 padding: 9px;
59 margin: 0 -10px 1em;
60 border-left: 1px solid #cde;
61 }
62 #readme blockquote :last-child {
63 margin-bottom: 0;
64 }
65 #readme h1 { font-size: 1.5em; }
66 #readme h2 { font-size: 1.4em; }
67 #readme h3 { font-size: 1.3em; }
68 #readme h4 { font-size: 1.2em; }
69 #readme h5 { font-size: 1.1em; }
70 #readme h6 { font-size: 1em; }
71
72 .breadcrumb {
73 padding: 0;
74 }
75 .breadcrumb a {
76 margin: 0;
77 color: #666;
78 font-size: 12px;
79 }
80 .breadcrumb .sep {
81 color: #bbb;
82 }
83 .breadcrumb img {
84 vertical-align: middle;
85 }
86 .breadcrumb h1 {
87 font-size: 12px;
88 font-weight: normal;
89 padding: 5px;
90 background: #f4f4f4;
91 border-top: 1px solid #ccc;
92 }
93
94 table {
95 border-collapse: collapse;
96 width: 100%;
97 background: #fff;
98 }
99 thead {
100 border-bottom: 1px solid #ccc;
101 }
102 th {
103 font-size: 11px;
104 height: 30px;
105 background: #ddd;
106 background: -webkit-gradient(
107 linear,
108 left bottom,
109 left top,
110 color-stop(0, #ddd),
111 color-stop(1, #eee)
112 );
113 background: -moz-linear-gradient(
114 center bottom,
115 #ddd 0%,
116 #eee 100%
117 );
118 }
119 th a:link, th a:visited {
120 margin: 0 10px;
121 color: #333;
122 }
123 th img {
124 position: absolute;
125 margin-left: -6px;
126 }
127 tbody {
128 border: solid #ccc;
129 border-width: 1px 0;
130 }
131 hr {
132 margin: 0;
133 border: none;
134 }
135 td {
136 padding: 5px;
137 font-size: 11px;
138 color: #333;
139 border-left: 1px dashed #eee;
140 }
141 td a {
142 margin-right: 40px;
143 font-size: 14px;
144 }
145 td.modified {
146 text-align: center;
147 }
148 td.size {
149 text-align: right;
150 }
151 .icon, .name {
152 border: none;
153 }
154 .icon {
155 width: 16px;
156 padding-right: 0;
157 }
158 .modified {
159 width: 120px;
160 }
161 .size {
162 width: 60px;
163 }
164
165 address {
166 padding: 5px;
167 font-size: 11px;
168 color: #333;
169 text-align: right;
170 }
flask_autoindex/static/desc.gif less more
Binary diff not shown
+0
-42
flask_autoindex/templates/__autoindex__/autoindex.html less more
0 {% from "__autoindex__/macros.html" import entry, thead, breadcrumb
1 with context %}
2
3 <!DOCTYPE html>
4 <html>
5 <head>
6 <meta charset="utf-8" />
7 <title>Index of {{ curdir.path }}</title>
8 {% block meta %}
9 <link rel="stylesheet" type="text/css"
10 href="{{ url_for('__autoindex__.static', filename='autoindex.css') }}" />
11 {% endblock %}
12 </head>
13 <body>
14 {% block header %}{% endblock %}
15 {% block table %}
16 <table>
17 <thead>
18 {{ thead() }}
19 {% if not curdir.is_root() %}
20 <tr>
21 <td class="breadcrumb" colspan="4">
22 <h1>{{ breadcrumb(curdir) }}</h1>
23 </td>
24 </tr>
25 {% endif %}
26 </thead>
27 <tbody>
28 {% for ent in entries %}
29 {{ entry(ent) }}
30 {% endfor %}
31 </tbody>
32 </table>
33 {% endblock %}
34 {% block footer %}
35 {% set env = request.environ %}
36 <address>{{ env.SERVER_SOFTWARE }}
37 Server at {{ env.HTTP_HOST }}
38 Port {{ env.SERVER_PORT }}</address>
39 {% endblock %}
40 </body>
41 </html>
+0
-70
flask_autoindex/templates/__autoindex__/macros.html less more
0 {% macro entry(ent) %}
1 <tr>
2 {% set icon = ent.guess_icon() %}
3 <td class="icon">
4 {% if icon %}
5 <img src="{{ icon }}" />
6 {% endif %}
7 </td>
8 <td class="name">
9 <a href="{{ url_for(endpoint, path=ent.path) }}">
10 {%- if ent.name == ".." -%}
11 Parent folder
12 {%- else -%}
13 {{ ent.name }}
14 {%- endif -%}
15 </a></td>
16 <td class="modified">
17 <time datetime="{{ ent.modified }}">{{ ent.modified }}</time>
18 </td>
19 <td class="size">
20 {% if ent.size %}
21 {{ ent.size|filesizeformat }}
22 {% else %}
23 -
24 {% endif %}
25 </td>
26 </tr>
27 {% endmacro %}
28
29 {% macro th(key, label, colspan=1) %}
30 <th class="{{ key }}" colspan="{{ colspan }}">
31 {%- if sort_by == key and order > 0 -%}
32 <a href="?sort_by={{ key }}&amp;order=desc">{{ label }}</a>
33 {%- else -%}
34 <a href="?sort_by={{ key }}">{{ label }}</a>
35 {%- endif -%}
36 {%- if sort_by == key -%}
37 {%- if order > 0 -%}
38 <img src="{{ url_for('__autoindex__.static', filename='asc.gif') }}" alt="ASC" />
39 {%- elif order < 0 -%}
40 <img src="{{ url_for('__autoindex__.static', filename='desc.gif') }}" alt="DESC" />
41 {%- endif -%}
42 {%- endif -%}
43 </th>
44 {% endmacro %}
45
46 {% macro thead() %}
47 <tr>
48 {{ th("name", "Name", 2) }}
49 {{ th("modified", "Last modified") }}
50 {{ th("size", "Size") }}
51 </tr>
52 {% endmacro %}
53
54 {% macro breadcrumb(ent) %}
55 {% set parent = ent.parent %}
56 {% if parent %}
57 {{ breadcrumb(parent) }}
58 <span class="sep">&raquo;</span>
59 {% endif %}
60 <a href="{{ url_for(endpoint, path=ent.path) }}">
61 {% set icon = ent.guess_icon() %}
62 {% if icon %}
63 <img src="{{ icon }}" />
64 {% endif %}
65 {% if not ent.is_root() %}
66 {{ ent.name }}
67 {% endif %}
68 </a>
69 {% endmacro %}
+0
-287
tests/__init__.py less more
0 from __future__ import absolute_import
1 from future.builtins import bytes
2 import mimetypes
3 import os
4 import sys
5 import unittest
6
7 from flask import *
8 from flask_autoindex import *
9
10
11 __file__ = __file__.replace('.pyc', '.py')
12 browse_root = os.path.abspath(os.path.dirname(__file__))
13
14
15 if sys.version_info < (3,):
16 b = lambda s: s
17 else:
18 b = lambda s: bytes(s, 'ascii')
19
20
21 class RootDirectoryTestCase(unittest.TestCase):
22
23 def setUp(self):
24 self.rootdir = RootDirectory(browse_root)
25
26 def test_root_dir(self):
27 assert isinstance(self.rootdir, RootDirectory)
28 assert self.rootdir.path == '.'
29 assert os.path.samefile(self.rootdir.abspath, browse_root)
30 assert os.path.isdir(self.rootdir.abspath)
31
32 def test_init(self):
33 assert isinstance(Directory(browse_root), RootDirectory)
34 assert isinstance(Directory('.', self.rootdir), RootDirectory)
35 assert isinstance(Entry(browse_root), RootDirectory)
36 assert isinstance(Entry('.', self.rootdir), RootDirectory)
37
38 def test_same_object(self):
39 assert self.rootdir is RootDirectory(browse_root)
40 assert self.rootdir is Directory(browse_root)
41 assert self.rootdir is Directory('.', self.rootdir)
42 assert self.rootdir is Entry(browse_root)
43 assert self.rootdir is Entry('.', self.rootdir)
44 assert RootDirectory(browse_root) is Directory(browse_root)
45 assert RootDirectory(browse_root) is Entry('.', self.rootdir)
46 assert Entry(browse_root) is Directory('.', self.rootdir)
47
48 def test_get_child_file(self):
49 file = self.rootdir.get_child('__init__.py')
50 assert isinstance(file, File)
51 assert file.ext == 'py'
52 assert file.path == '__init__.py'
53 assert file.rootdir is self.rootdir
54
55 def test_get_child_dir(self):
56 dir = self.rootdir.get_child('static')
57 assert isinstance(dir, Directory)
58 assert dir.path == 'static'
59 assert dir.rootdir is self.rootdir
60
61 def test_contain(self):
62 assert 'static' in self.rootdir
63 assert '__init__.py' in self.rootdir
64 assert Entry('static', self.rootdir) in self.rootdir
65 assert Entry('__init__.py', self.rootdir) in self.rootdir
66 assert '^_^' not in self.rootdir
67
68
69 class DirectoryTestCase(unittest.TestCase):
70
71 def setUp(self):
72 self.rootdir = RootDirectory(browse_root)
73 self.static = Directory('static', self.rootdir)
74
75 def test_dir(self):
76 assert isinstance(self.static, Directory)
77 assert self.static.path == 'static'
78 assert os.path.samefile(self.static.abspath,
79 os.path.join(browse_root, 'static'))
80 assert os.path.isdir(self.static.abspath)
81
82 def test_init(self):
83 assert isinstance(Directory('static', self.rootdir), Directory)
84 assert isinstance(Entry('static', self.rootdir), Directory)
85
86 def test_same_object(self):
87 assert self.static is Directory('static', self.rootdir)
88 assert self.static is Entry('static', self.rootdir)
89 assert Directory('static', self.rootdir) is \
90 Entry('static', self.rootdir)
91 assert self.static.parent is self.rootdir
92
93 def test_get_child_file(self):
94 file = self.static.get_child('test.txt')
95 assert isinstance(file, File)
96 assert file.ext == 'txt'
97 assert file.path == 'static/test.txt'
98 assert file.rootdir is self.rootdir
99
100 def test_contain(self):
101 assert 'test.py' in self.static
102 assert Entry('static/test.py', self.rootdir) in self.static
103 assert '^_^' not in self.static
104
105
106 class FileTestCase(unittest.TestCase):
107
108 def setUp(self):
109 self.rootdir = RootDirectory(browse_root)
110 self.itself = File('__init__.py', self.rootdir)
111
112 def test_file(self):
113 assert isinstance(self.itself, File)
114 assert self.itself.path == '__init__.py'
115 assert os.path.samefile(self.itself.abspath,
116 os.path.join(browse_root, '__init__.py'))
117 assert os.path.isfile(self.itself.abspath)
118
119 def test_init(self):
120 assert isinstance(File('__init__.py', self.rootdir), File)
121 assert isinstance(Entry('__init__.py', self.rootdir), File)
122 assert isinstance(File('static/test.txt', self.rootdir), File)
123 assert isinstance(Entry('static/test.txt', self.rootdir), File)
124
125 def test_same_object(self):
126 assert self.itself is File('__init__.py', self.rootdir)
127 assert self.itself is Entry('__init__.py', self.rootdir)
128 assert File('__init__.py', self.rootdir) is \
129 Entry('__init__.py', self.rootdir)
130
131 def test_properties(self):
132 with open(__file__) as f:
133 source = ''.join(f.readlines())
134 assert self.itself.data.strip() == source.strip()
135 assert self.itself.size == len(source)
136 assert self.itself.ext == 'py'
137 assert self.itself.mimetype == mimetypes.guess_type(__file__)
138
139
140 class ApplicationTestCase(unittest.TestCase):
141
142 def setUp(self):
143 self.app = Flask(__name__)
144 self.app2 = Flask(__name__)
145 self.idx = AutoIndex(self.app, browse_root, add_url_rules=True)
146 self.idx2 = AutoIndex(self.app2, browse_root,
147 silk_options={'silk_path': '/myicons'})
148 @self.app2.route('/')
149 @self.app2.route('/<path:path>')
150 def autoindex_test(path='.'):
151 return self.idx2.render_autoindex(path, browse_root)
152
153 def get(self, path):
154 return self.app.test_client().get(path)
155
156 def get2(self, path):
157 self.app2.config['TESTING'] = True
158 return self.app2.test_client().get(path)
159
160 def test_css(self):
161 for get in [self.get, self.get2]:
162 rv = get('/__autoindex__/autoindex.css')
163 assert 200 == rv.status_code
164
165 def test_icon(self):
166 rv = self.get('/__icons__/page_white.png')
167 rv2 = self.get2('/myicons/page_white.png')
168 assert 294 == len(rv.data)
169 assert rv.data == rv2.data
170
171 def test_autoindex(self):
172
173 assert b('__init__.py') in self.get('/').data
174 assert b('__init__.py') in self.get2('/').data
175
176 def test_own_static_file(self):
177 rv = self.get('/static/helloworld.txt')
178 assert b('Hello, world!') == rv.data.strip()
179
180 def test_own_page(self):
181 for get in [self.get, self.get2]:
182 rv = get('/test')
183 assert not b('foo bar foo bar') == rv.data.strip()
184 @self.app.route('/test')
185 def sublee():
186 return 'foo bar foo bar', 200
187 @self.app2.route('/test')
188 def sublee():
189 return 'foo bar foo bar', 200
190 for get in [self.get, self.get2]:
191 rv = get('/test')
192 assert b('foo bar foo bar') == rv.data.strip()
193
194 def test_builtin_icon_rule(self):
195 testset = {'7z': 'page_white_zip.png',
196 'avi': 'film.png',
197 'cer': 'key.png',
198 'html': 'page_white_code.png',
199 'iso': 'cd.png',
200 'rss': 'feed.png'}
201 with self.app.test_request_context():
202 for ext, icon in list(testset.items()):
203 file = self.idx.rootdir.get_child('static/test.' + ext)
204 assert file.guess_icon().endswith(icon)
205
206 def test_custom_icon_rule(self):
207 with self.app.test_request_context():
208 file = self.idx.rootdir.get_child('__init__.py')
209 original_icon_url = file.guess_icon()
210 self.idx.add_icon_rule('table.png', ext='py')
211 customized_icon_url = file.guess_icon()
212 assert original_icon_url.endswith('page_white_python.png')
213 assert customized_icon_url.endswith('table.png')
214
215
216 class SubdomainTestCase(unittest.TestCase):
217
218 def setUp(self):
219 from .blueprinttest import bp
220 app = Flask(__name__)
221 app.config['SERVER_NAME'] = 'example.org'
222 AutoIndex(bp, browse_root)
223 app.register_blueprint(bp, subdomain='test')
224 self.app = app
225
226 def get(self, path):
227 return self.app.test_client().get(path, 'http://test.example.org/')
228
229 def test_css(self):
230 rv = self.get('/static/autoindex.css')
231 assert 200 == rv.status_code, 'could not found preloaded css file.'
232
233 def test_icon(self):
234 rv = self.get('/__icons__/page_white.png')
235 assert 294 == len(rv.data), 'could not found preloaded icon file.'
236
237 def test_browse(self):
238 rv = self.get('/')
239 assert 'Index of /' in rv.data
240 assert '__init__.py' in rv.data
241
242 def test_own_static_file(self):
243 rv = self.get('/static/helloworld.txt')
244 assert 'Hello, world!' == rv.data.strip()
245
246
247 class WithoutSubdomainTestCase(unittest.TestCase):
248
249 def setUp(self):
250 from .blueprinttest import bp
251 app = Flask(__name__)
252 AutoIndex(bp, browse_root)
253 app.register_blueprint(bp)
254 self.app = app
255
256 def get(self, path):
257 return self.app.test_client().get(path)
258
259 def test_css(self):
260 rv = self.get('/static/autoindex.css')
261 assert 200 == rv.status_code, 'could not found preloaded css file.'
262
263 def test_icon(self):
264 rv = self.get('/__icons__/page_white.png')
265 assert 294 == len(rv.data), 'could not found preloaded icon file.'
266
267 def test_browse(self):
268 rv = self.get('/')
269 assert 'Index of /' in rv.data
270 assert '__init__.py' in rv.data
271
272
273 def suite():
274 suite = unittest.TestSuite()
275 suite.addTest(unittest.makeSuite(RootDirectoryTestCase))
276 suite.addTest(unittest.makeSuite(DirectoryTestCase))
277 suite.addTest(unittest.makeSuite(FileTestCase))
278 suite.addTest(unittest.makeSuite(ApplicationTestCase))
279 # These cases will be passed on Flask next generation.
280 # suite.addTest(unittest.makeSuite(SubdomainTestCase))
281 # suite.addTest(unittest.makeSuite(WithoutSubdomainTestCase))
282 return suite
283
284
285 if __name__ == '__main__':
286 unittest.main(defaultTest='suite')
+0
-2
tests/blueprinttest/__init__.py less more
0 from flask import Blueprint
1 bp = Blueprint('test', __name__)
+0
-1
tests/static/helloworld.txt less more
0 Hello, world!
+0
-0
tests/static/test.7z less more
(Empty file)
+0
-0
tests/static/test.ai less more
(Empty file)
+0
-0
tests/static/test.alz less more
(Empty file)
+0
-0
tests/static/test.app less more
(Empty file)
+0
-0
tests/static/test.applescript less more
(Empty file)
+0
-0
tests/static/test.asp less more
(Empty file)
+0
-0
tests/static/test.avi less more
(Empty file)
+0
-0
tests/static/test.bak less more
(Empty file)
+0
-0
tests/static/test.bat less more
(Empty file)
+0
-0
tests/static/test.bin less more
(Empty file)
+0
-0
tests/static/test.bmp less more
(Empty file)
+0
-0
tests/static/test.bup less more
(Empty file)
+0
-0
tests/static/test.c less more
(Empty file)
+0
-0
tests/static/test.cab less more
(Empty file)
+0
-0
tests/static/test.cer less more
(Empty file)
+0
-0
tests/static/test.cfg less more
(Empty file)
+0
-0
tests/static/test.cgi less more
(Empty file)
+0
-0
tests/static/test.com less more
(Empty file)
+0
-0
tests/static/test.conf less more
(Empty file)
+0
-0
tests/static/test.cpl less more
(Empty file)
+0
-0
tests/static/test.cpp less more
(Empty file)
+0
-0
tests/static/test.css less more
(Empty file)
+0
-0
tests/static/test.csv less more
(Empty file)
+0
-0
tests/static/test.cur less more
(Empty file)
+0
-0
tests/static/test.db less more
(Empty file)
+0
-0
tests/static/test.dll less more
(Empty file)
+0
-0
tests/static/test.dmg less more
(Empty file)
+0
-0
tests/static/test.doc less more
(Empty file)
+0
-0
tests/static/test.docx less more
(Empty file)
+0
-0
tests/static/test.eps less more
(Empty file)
+0
-0
tests/static/test.exe less more
(Empty file)
+0
-0
tests/static/test.fla less more
(Empty file)
+0
-0
tests/static/test.flv less more
(Empty file)
+0
-0
tests/static/test.gif less more
(Empty file)
+0
-0
tests/static/test.h less more
(Empty file)
+0
-0
tests/static/test.htm less more
(Empty file)
+0
-0
tests/static/test.html less more
(Empty file)
+0
-0
tests/static/test.hwp less more
(Empty file)
+0
-0
tests/static/test.ico less more
(Empty file)
+0
-0
tests/static/test.ics less more
(Empty file)
+0
-0
tests/static/test.ini less more
(Empty file)
+0
-0
tests/static/test.iso less more
(Empty file)
+0
-0
tests/static/test.jar less more
(Empty file)
+0
-0
tests/static/test.java less more
(Empty file)
+0
-0
tests/static/test.jpeg less more
(Empty file)
+0
-0
tests/static/test.jpg less more
(Empty file)
+0
-0
tests/static/test.js less more
(Empty file)
+0
-0
tests/static/test.json less more
(Empty file)
+0
-0
tests/static/test.jsp less more
(Empty file)
+0
-0
tests/static/test.less less more
(Empty file)
+0
-0
tests/static/test.log less more
(Empty file)
+0
-0
tests/static/test.markdown less more
(Empty file)
+0
-0
tests/static/test.md less more
(Empty file)
+0
-0
tests/static/test.mid less more
(Empty file)
+0
-0
tests/static/test.mov less more
(Empty file)
+0
-0
tests/static/test.mp3 less more
(Empty file)
+0
-0
tests/static/test.mp4 less more
(Empty file)
+0
-0
tests/static/test.mpa less more
(Empty file)
+0
-0
tests/static/test.mpeg less more
(Empty file)
+0
-0
tests/static/test.mpg less more
(Empty file)
+0
-0
tests/static/test.pdf less more
(Empty file)
+0
-0
tests/static/test.php less more
(Empty file)
+0
-0
tests/static/test.pkg less more
(Empty file)
+0
-0
tests/static/test.png less more
(Empty file)
+0
-0
tests/static/test.ppt less more
(Empty file)
+0
-0
tests/static/test.pptx less more
(Empty file)
+0
-0
tests/static/test.psd less more
(Empty file)
+0
-0
tests/static/test.py less more
(Empty file)
+0
-0
tests/static/test.rar less more
(Empty file)
+0
-0
tests/static/test.rb less more
(Empty file)
+0
-0
tests/static/test.rss less more
(Empty file)
+0
-0
tests/static/test.rtf less more
(Empty file)
+0
-0
tests/static/test.sh less more
(Empty file)
+0
-0
tests/static/test.smi less more
(Empty file)
+0
-0
tests/static/test.sql less more
(Empty file)
+0
-0
tests/static/test.svg less more
(Empty file)
+0
-0
tests/static/test.swf less more
(Empty file)
+0
-0
tests/static/test.sys less more
(Empty file)
+0
-0
tests/static/test.tar less more
(Empty file)
+0
-0
tests/static/test.tar.gz less more
(Empty file)
+0
-0
tests/static/test.tgz less more
(Empty file)
+0
-0
tests/static/test.tif less more
(Empty file)
+0
-0
tests/static/test.tmp less more
(Empty file)
+0
-0
tests/static/test.toast less more
(Empty file)
+0
-0
tests/static/test.torrent less more
(Empty file)
+0
-0
tests/static/test.ttf less more
(Empty file)
+0
-0
tests/static/test.txt less more
(Empty file)
+0
-0
tests/static/test.vb less more
(Empty file)
+0
-0
tests/static/test.vcd less more
(Empty file)
+0
-0
tests/static/test.vcf less more
(Empty file)
+0
-0
tests/static/test.wav less more
(Empty file)
+0
-0
tests/static/test.wmv less more
(Empty file)
+0
-0
tests/static/test.xhtml less more
(Empty file)
+0
-0
tests/static/test.xls less more
(Empty file)
+0
-0
tests/static/test.xlsx less more
(Empty file)
+0
-0
tests/static/test.xml less more
(Empty file)
+0
-0
tests/static/test.xsl less more
(Empty file)
+0
-0
tests/static/test.yml less more
(Empty file)
+0
-0
tests/static/test.zip less more
(Empty file)