Mercurial > cgi-bin > hgweb.cgi > tincan
comparison tincan.py @ 59:60907204a265 draft
Support case-insensitive filesystems properly.
author | David Barts <n5jrn@me.com> |
---|---|
date | Fri, 31 May 2019 21:20:22 -0700 |
parents | e08e24707da1 |
children | f33cb3e93473 |
comparison
equal
deleted
inserted
replaced
58:e08e24707da1 | 59:60907204a265 |
---|---|
250 else: | 250 else: |
251 ret.append(ch) | 251 ret.append(ch) |
252 first = False | 252 first = False |
253 return ''.join(ret) | 253 return ''.join(ret) |
254 | 254 |
255 _FOLDS_CASE = sys.platform in ['darwin', 'win32'] | |
256 | |
257 def _casef(string, case="lower"): | |
258 """ | |
259 If we're on an OS with case-insensitive file names, fold case. | |
260 Else leave things alone. | |
261 """ | |
262 return getattr(string, case)() if _FOLDS_CASE else string | |
263 | |
255 # The TinCan class. Simply a Bottle webapp that contains a forward method, so | 264 # The TinCan class. Simply a Bottle webapp that contains a forward method, so |
256 # the code-behind can call request.app.forward(). | 265 # the code-behind can call request.app.forward(). |
257 | 266 |
258 class TinCan(bottle.Bottle): | 267 class TinCan(bottle.Bottle): |
259 def forward(self, target): | 268 def forward(self, target): |
351 self.fname = raw[equals+1:] | 360 self.fname = raw[equals+1:] |
352 if self.vname == "": | 361 if self.vname == "": |
353 raise ValueError("empty variable name") | 362 raise ValueError("empty variable name") |
354 if self.fname == "": | 363 if self.fname == "": |
355 raise ValueError("empty file name") | 364 raise ValueError("empty file name") |
356 if not self.fname.endswith(_IEXTEN): | 365 if not _casef(self.fname).endswith(_IEXTEN): |
357 raise ValueError("file does not end in {0}".format(_IEXTEN)) | 366 raise ValueError("file does not end in {0}".format(_IEXTEN)) |
358 | 367 |
359 # Using a cache is likely to help efficiency a lot, since many pages | 368 # Using a cache is likely to help efficiency a lot, since many pages |
360 # will typically #load the same standard stuff. Except if we're | 369 # will typically #load the same standard stuff. Except if we're |
361 # multithreading, then we want each page's templates to be private. | 370 # multithreading, then we want each page's templates to be private. |
442 | 451 |
443 def launch(self): | 452 def launch(self): |
444 self.logger.info("adding static route: %s", self._urlpath) | 453 self.logger.info("adding static route: %s", self._urlpath) |
445 self._app.route(self._urlpath, 'GET', self) | 454 self._app.route(self._urlpath, 'GET', self) |
446 for i in _INDICES: | 455 for i in _INDICES: |
447 if self._urlpath.endswith(i): | 456 if _casef(self._urlpath).endswith(i): |
448 li = len(i) | 457 li = len(i) |
449 for j in [ self._urlpath[:1-li], self._urlpath[:-li] ]: | 458 for j in [ self._urlpath[:1-li], self._urlpath[:-li] ]: |
450 if j: | 459 if j: |
451 self.logger.info("adding static route: %s", j) | 460 self.logger.info("adding static route: %s", j) |
452 self._app.route(j, 'GET', self) | 461 self._app.route(j, 'GET', self) |
580 return | 589 return |
581 # Get the code-behind #python | 590 # Get the code-behind #python |
582 if self._header.python is None: | 591 if self._header.python is None: |
583 self._python_specified = False | 592 self._python_specified = False |
584 else: | 593 else: |
585 if not self._header.python.endswith(_PEXTEN): | 594 if not _casef(self._header.python).endswith(_PEXTEN): |
586 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) | 595 raise TinCanError("{0}: #python files must end in {1}".format(self._urlpath, _PEXTEN)) |
587 self._python = self._header.python | 596 self._python = self._header.python |
588 self._python_specified = True | 597 self._python_specified = True |
589 # Obtain a class object by importing and introspecting a module. | 598 # Obtain a class object by importing and introspecting a module. |
590 self._getclass() | 599 self._getclass() |
591 # Build body object (#template) and obtain #loads. | 600 # Build body object (#template) and obtain #loads. |
592 if self._header.template is not None: | 601 if self._header.template is not None: |
593 if not self._header.template.endswith(_TEXTEN): | 602 if not _casef(self._header.template).endswith(_TEXTEN): |
594 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) | 603 raise TinCanError("{0}: #template files must end in {1}".format(self._urlpath, _TEXTEN)) |
595 try: | 604 try: |
596 rtpath = self._splitpath(self._header.template) | 605 rtpath = self._splitpath(self._header.template) |
597 tpath = os.path.normpath(os.path.join(self._fsroot, *rtpath)) | 606 tpath = os.path.normpath(os.path.join(self._fsroot, *rtpath)) |
598 tfile = TemplateFile(tpath) | 607 tfile = TemplateFile(tpath) |
633 raise TinCanError("{0}: no #methods specified".format(self._urlpath)) | 642 raise TinCanError("{0}: no #methods specified".format(self._urlpath)) |
634 # Register this thing with Bottle | 643 # Register this thing with Bottle |
635 mtxt = ','.join(methods) | 644 mtxt = ','.join(methods) |
636 self.logger.info("adding route: %s (%s)", self._origin, mtxt) | 645 self.logger.info("adding route: %s (%s)", self._origin, mtxt) |
637 self._app.route(self._origin, methods, self) | 646 self._app.route(self._origin, methods, self) |
638 if self._origin.endswith(_INDEX): | 647 if _casef(self._origin).endswith(_INDEX): |
639 for i in [ self._origin[:1-_LINDEX], self._origin[:-_LINDEX] ]: | 648 for i in [ self._origin[:1-_LINDEX], self._origin[:-_LINDEX] ]: |
640 if i: | 649 if i: |
641 self.logger.info("adding route: %s (%s)", i, mtxt) | 650 self.logger.info("adding route: %s (%s)", i, mtxt) |
642 self._app.route(i, methods, self) | 651 self._app.route(i, methods, self) |
643 | 652 |
749 self._seen.add(forw) | 758 self._seen.add(forw) |
750 rname = rlist.pop() | 759 rname = rlist.pop() |
751 except IndexError as e: | 760 except IndexError as e: |
752 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e | 761 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) from e |
753 name, ext = os.path.splitext(rname) | 762 name, ext = os.path.splitext(rname) |
754 if ext != _TEXTEN: | 763 if _casef(ext) != _TEXTEN: |
755 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) | 764 raise TinCanError("{0}: invalid #forward".format(self._urlpath)) |
756 self._subdir = rlist | 765 self._subdir = rlist |
757 self._python = name + _PEXTEN | 766 self._python = name + _PEXTEN |
758 self._fspath = os.path.join(self._fsroot, *self._subdir, rname) | 767 self._fspath = os.path.join(self._fsroot, *self._subdir, rname) |
759 self._urlpath = '/' + self.urljoin(*self._subdir, rname) | 768 self._urlpath = '/' + self.urljoin(*self._subdir, rname) |
818 return False | 827 return False |
819 | 828 |
820 # L a u n c h e r | 829 # L a u n c h e r |
821 | 830 |
822 _WINF = "WEB-INF" | 831 _WINF = "WEB-INF" |
823 _BANNED = set([_WINF]) | |
824 _EBANNED = set([_IEXTEN, _TEXTEN, _PEXTEN, _PEXTEN+"c"]) | 832 _EBANNED = set([_IEXTEN, _TEXTEN, _PEXTEN, _PEXTEN+"c"]) |
825 ENCODING = "utf-8" | 833 ENCODING = "utf-8" |
826 _BITBUCKET = logging.getLogger(__name__) | 834 _BITBUCKET = logging.getLogger(__name__) |
827 _BITBUCKET.addHandler(logging.NullHandler) | 835 _BITBUCKET.addHandler(logging.NullHandler) |
828 | 836 |
884 | 892 |
885 def _launch(self, subdir): | 893 def _launch(self, subdir): |
886 for entry in os.listdir(os.path.join(self.fsroot, *subdir)): | 894 for entry in os.listdir(os.path.join(self.fsroot, *subdir)): |
887 if entry.startswith("."): | 895 if entry.startswith("."): |
888 continue # hidden file | 896 continue # hidden file |
889 if not subdir and entry in _BANNED: | 897 if not subdir and _casef(entry, "upper") == _WINF: |
890 continue | 898 continue |
891 etype = os.stat(os.path.join(self.fsroot, *subdir, entry)).st_mode | 899 etype = os.stat(os.path.join(self.fsroot, *subdir, entry)).st_mode |
892 if S_ISREG(etype): | 900 if S_ISREG(etype): |
893 ename, eext = os.path.splitext(entry) | 901 ename, eext = os.path.splitext(entry) |
902 eext = _casef(eext) | |
894 if eext == _TEXTEN: | 903 if eext == _TEXTEN: |
895 route = _TinCanRoute(self, ename, subdir) | 904 route = _TinCanRoute(self, ename, subdir) |
896 else: | 905 else: |
897 if eext in _EBANNED: | 906 if eext in _EBANNED: |
898 continue | 907 continue |