Mercurial > hg > martINI
comparison martini/web.py @ 0:3c3522ce6e3a
initial import of martINI from https://svn.openplans.org/svn/standalone/martINI/
| author | k0s <k0scist@gmail.com> |
|---|---|
| date | Tue, 08 Dec 2009 15:13:28 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:3c3522ce6e3a |
|---|---|
| 1 """ | |
| 2 a view with webob + genshi for editing and viewing .ini files | |
| 3 """ | |
| 4 import os | |
| 5 import re | |
| 6 | |
| 7 from genshi.template import TemplateLoader | |
| 8 from martini.config import ConfigMunger | |
| 9 from martini.utils import getbool | |
| 10 from martini.utils import getlist | |
| 11 from paste.httpexceptions import HTTPExceptionHandler | |
| 12 from paste.urlparser import StaticURLParser | |
| 13 from pkg_resources import resource_filename | |
| 14 from webob import Request, Response, exc | |
| 15 | |
| 16 class MartiniWebView(object): | |
| 17 | |
| 18 ### class level variables | |
| 19 defaults = { 'auto_reload': 'True', | |
| 20 'files': None, | |
| 21 'directories': None } | |
| 22 | |
| 23 def __init__(self, **kw): | |
| 24 | |
| 25 # set instance parameters from kw and defaults | |
| 26 for key in self.defaults: | |
| 27 setattr(self, key, kw.get(key, self.defaults[key])) | |
| 28 self.auto_reload = getbool(self.auto_reload) | |
| 29 | |
| 30 # files | |
| 31 self.files = getlist(self.files) | |
| 32 if self.files: | |
| 33 assert [ f for f in self.files if os.path.isabs(f) ] | |
| 34 self.files = dict([(os.path.basename(f), f) | |
| 35 for f in self.files]) | |
| 36 else: | |
| 37 self.files = {} | |
| 38 | |
| 39 # directories | |
| 40 self.directories = getlist(self.directories) | |
| 41 if self.directories: | |
| 42 assert [ d for d in self.directories if os.path.isabs(d) ] | |
| 43 if len(self.directories) > 1 or self.files: | |
| 44 # namespace directories | |
| 45 self.directories = dict([(os.path.basename(d), d) | |
| 46 for d in self.directories]) | |
| 47 else: | |
| 48 # don't namespace a single directory | |
| 49 self.directories = { '': self.directories[0] } | |
| 50 else: | |
| 51 self.directories = {} | |
| 52 | |
| 53 # have to have something to serve! | |
| 54 assert self.files or self.directories | |
| 55 | |
| 56 # methods to respond to | |
| 57 self.response_functions = { 'GET': self.get, | |
| 58 'POST': self.post, | |
| 59 } | |
| 60 | |
| 61 # template loader | |
| 62 templates_dir = resource_filename(__name__, 'templates') | |
| 63 self.loader = TemplateLoader(templates_dir, | |
| 64 auto_reload=self.auto_reload) | |
| 65 | |
| 66 # fileserver | |
| 67 self.fileserver = StaticURLParser(resource_filename(__name__, 'static')) | |
| 68 | |
| 69 def file(self, path): | |
| 70 path = path.strip('/') | |
| 71 if not path: | |
| 72 return None | |
| 73 f = self.files.get(path, None) | |
| 74 if f is None: | |
| 75 if path in self.directories: | |
| 76 return self.directories[path] | |
| 77 if '/' in path: | |
| 78 path = path.split('/', 1)[-1] | |
| 79 for d in self.directories.values(): | |
| 80 filename = os.path.join(d, path) | |
| 81 if os.path.exists(filename): | |
| 82 return filename | |
| 83 else: | |
| 84 return f | |
| 85 | |
| 86 ### methods dealing with HTTP | |
| 87 def __call__(self, environ, start_response): | |
| 88 request = Request(environ) | |
| 89 | |
| 90 if request.path_info.strip('/') and os.path.exists(os.path.join(resource_filename(__name__, 'static'), request.path_info.strip('/'))): | |
| 91 return self.fileserver(environ, start_response) | |
| 92 | |
| 93 if request.path_info.endswith('/'): | |
| 94 if request.path_info.strip('/'): | |
| 95 raise exc.HTTPFound(location=request.path_info.rstrip('/')) | |
| 96 else: | |
| 97 if request.path_info != '/': | |
| 98 raise exc.HTTPFound(location='/') | |
| 99 res = self.make_response(request) | |
| 100 return res(environ, start_response) | |
| 101 | |
| 102 def make_response(self, request): | |
| 103 return self.response_functions.get(request.method, self.error)(request) | |
| 104 | |
| 105 def get_response(self, text, content_type='text/html'): | |
| 106 """make an HTTP response from text""" | |
| 107 res = Response(content_type=content_type, body=text) | |
| 108 return res | |
| 109 | |
| 110 def get(self, request): | |
| 111 """ | |
| 112 return response to a GET requst | |
| 113 """ | |
| 114 | |
| 115 # index of all resources | |
| 116 if not request.path_info.strip('/'): | |
| 117 template = self.loader.load('index.html') | |
| 118 items = self.files.keys() + self.directories.keys() | |
| 119 res = template.generate(request=request, items=items).render('html', doctype='html') | |
| 120 return self.get_response(res) | |
| 121 | |
| 122 # get the file | |
| 123 f = self.file(request.path_info) | |
| 124 if not f: | |
| 125 raise exc.HTTPNotFound() | |
| 126 | |
| 127 # index page of a directory | |
| 128 if os.path.isdir(f): | |
| 129 template = self.loader.load('index.html') | |
| 130 items = os.listdir(f) | |
| 131 res = template.generate(request=request, items=items).render('html', doctype='html') | |
| 132 return self.get_response(res) | |
| 133 | |
| 134 # get configuration | |
| 135 conf = ConfigMunger(f) | |
| 136 | |
| 137 # render the template | |
| 138 template = self.loader.load('table.html') | |
| 139 res = template.generate(request=request, sections=conf.dict()).render('html', doctype='html') | |
| 140 # generate the response | |
| 141 return self.get_response(res) | |
| 142 | |
| 143 def post(self, request): | |
| 144 """ | |
| 145 return response to a POST request | |
| 146 """ | |
| 147 | |
| 148 if not request.path_info.strip('/'): | |
| 149 raise exc.HTTPMethodNotAllowed() | |
| 150 | |
| 151 # get the file | |
| 152 f = self.file(request.path_info) | |
| 153 if not f: | |
| 154 raise exc.HTTPNotFound() | |
| 155 if os.path.isdir(f): | |
| 156 raise exc.HTTPMethodNotAllowed() | |
| 157 | |
| 158 regex = '\[([^\]]+)\](.*)' | |
| 159 | |
| 160 conf = ConfigMunger(f) | |
| 161 | |
| 162 delete = request.POST.getall('delete') | |
| 163 if delete: | |
| 164 del request.POST['delete'] | |
| 165 | |
| 166 for key, value in request.POST.items(): | |
| 167 value = ' '.join(value.strip().split()) | |
| 168 match = re.match(regex, key) | |
| 169 section, option = match.groups() | |
| 170 if option: | |
| 171 conf.set(section, option, value) | |
| 172 else: | |
| 173 if value: | |
| 174 conf.move_section(section, value) | |
| 175 else: | |
| 176 conf.add_section(section) | |
| 177 | |
| 178 for d in delete: | |
| 179 match = re.match(regex, d) | |
| 180 section, option = match.groups() | |
| 181 if conf.has_section(section): | |
| 182 if option: | |
| 183 conf.remove_option(section, option) | |
| 184 else: | |
| 185 conf.remove_section(section) | |
| 186 | |
| 187 output = file(f, 'w') | |
| 188 conf.write(output) | |
| 189 | |
| 190 return exc.HTTPSeeOther(location=request.path_info) | |
| 191 | |
| 192 def error(self, request): | |
| 193 """deal with non-supported methods""" | |
| 194 return exc.HTTPMethodNotAllowed("Only %r operations are allowed" % self.response_functions.keys()) | |
| 195 | |
| 196 | |
| 197 ### paste factory | |
| 198 | |
| 199 def factory(global_conf, **app_conf): | |
| 200 """create a webob view and wrap it in middleware""" | |
| 201 keystr = 'martini.' | |
| 202 args = dict([(key.split(keystr, 1)[-1], value) | |
| 203 for key, value in app_conf.items() | |
| 204 if key.startswith(keystr) ]) | |
| 205 app = MartiniWebView(**args) | |
| 206 return HTTPExceptionHandler(app) |
