Update tracked files
Jonathan Carter
6 years ago
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 | 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 | # -*- 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 | 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() |
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 | } |
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 | {% 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 }}&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">»</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 | 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') |