Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

urlpath.py 8.2KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # -*- test-case-name: twisted.python.test.test_urlpath -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. L{URLPath}, a representation of a URL.
  6. """
  7. from typing import cast
  8. from urllib.parse import quote as urlquote, unquote as urlunquote, urlunsplit
  9. from hyperlink import URL as _URL
  10. _allascii = b"".join([chr(x).encode("ascii") for x in range(1, 128)])
  11. def _rereconstituter(name):
  12. """
  13. Attriute declaration to preserve mutability on L{URLPath}.
  14. @param name: a public attribute name
  15. @type name: native L{str}
  16. @return: a descriptor which retrieves the private version of the attribute
  17. on get and calls rerealize on set.
  18. """
  19. privateName = "_" + name
  20. return property(
  21. lambda self: getattr(self, privateName),
  22. lambda self, value: (
  23. setattr(
  24. self,
  25. privateName,
  26. value if isinstance(value, bytes) else value.encode("charmap"),
  27. )
  28. or self._reconstitute()
  29. ),
  30. )
  31. class URLPath:
  32. """
  33. A representation of a URL.
  34. @ivar scheme: The scheme of the URL (e.g. 'http').
  35. @type scheme: L{bytes}
  36. @ivar netloc: The network location ("host").
  37. @type netloc: L{bytes}
  38. @ivar path: The path on the network location.
  39. @type path: L{bytes}
  40. @ivar query: The query argument (the portion after ? in the URL).
  41. @type query: L{bytes}
  42. @ivar fragment: The page fragment (the portion after # in the URL).
  43. @type fragment: L{bytes}
  44. """
  45. def __init__(
  46. self, scheme=b"", netloc=b"localhost", path=b"", query=b"", fragment=b""
  47. ):
  48. self._scheme = scheme or b"http"
  49. self._netloc = netloc
  50. self._path = path or b"/"
  51. self._query = query
  52. self._fragment = fragment
  53. self._reconstitute()
  54. def _reconstitute(self):
  55. """
  56. Reconstitute this L{URLPath} from all its given attributes.
  57. """
  58. urltext = urlquote(
  59. urlunsplit(
  60. (self._scheme, self._netloc, self._path, self._query, self._fragment)
  61. ),
  62. safe=_allascii,
  63. )
  64. self._url = _URL.fromText(urltext.encode("ascii").decode("ascii"))
  65. scheme = _rereconstituter("scheme")
  66. netloc = _rereconstituter("netloc")
  67. path = _rereconstituter("path")
  68. query = _rereconstituter("query")
  69. fragment = _rereconstituter("fragment")
  70. @classmethod
  71. def _fromURL(cls, urlInstance):
  72. """
  73. Reconstruct all the public instance variables of this L{URLPath} from
  74. its underlying L{_URL}.
  75. @param urlInstance: the object to base this L{URLPath} on.
  76. @type urlInstance: L{_URL}
  77. @return: a new L{URLPath}
  78. """
  79. self = cls.__new__(cls)
  80. self._url = urlInstance.replace(path=urlInstance.path or [""])
  81. self._scheme = self._url.scheme.encode("ascii")
  82. self._netloc = self._url.authority().encode("ascii")
  83. self._path = (
  84. _URL(path=self._url.path, rooted=True).asURI().asText().encode("ascii")
  85. )
  86. self._query = (_URL(query=self._url.query).asURI().asText().encode("ascii"))[1:]
  87. self._fragment = self._url.fragment.encode("ascii")
  88. return self
  89. def pathList(self, unquote=False, copy=True):
  90. """
  91. Split this URL's path into its components.
  92. @param unquote: whether to remove %-encoding from the returned strings.
  93. @param copy: (ignored, do not use)
  94. @return: The components of C{self.path}
  95. @rtype: L{list} of L{bytes}
  96. """
  97. segments = self._url.path
  98. mapper = lambda x: x.encode("ascii")
  99. if unquote:
  100. mapper = lambda x, m=mapper: m(urlunquote(x))
  101. return [b""] + [mapper(segment) for segment in segments]
  102. @classmethod
  103. def fromString(klass, url):
  104. """
  105. Make a L{URLPath} from a L{str} or L{unicode}.
  106. @param url: A L{str} representation of a URL.
  107. @type url: L{str} or L{unicode}.
  108. @return: a new L{URLPath} derived from the given string.
  109. @rtype: L{URLPath}
  110. """
  111. if not isinstance(url, str):
  112. raise ValueError("'url' must be a str")
  113. return klass._fromURL(_URL.fromText(url))
  114. @classmethod
  115. def fromBytes(klass, url):
  116. """
  117. Make a L{URLPath} from a L{bytes}.
  118. @param url: A L{bytes} representation of a URL.
  119. @type url: L{bytes}
  120. @return: a new L{URLPath} derived from the given L{bytes}.
  121. @rtype: L{URLPath}
  122. @since: 15.4
  123. """
  124. if not isinstance(url, bytes):
  125. raise ValueError("'url' must be bytes")
  126. quoted = urlquote(url, safe=_allascii)
  127. return klass.fromString(quoted)
  128. @classmethod
  129. def fromRequest(klass, request):
  130. """
  131. Make a L{URLPath} from a L{twisted.web.http.Request}.
  132. @param request: A L{twisted.web.http.Request} to make the L{URLPath}
  133. from.
  134. @return: a new L{URLPath} derived from the given request.
  135. @rtype: L{URLPath}
  136. """
  137. return klass.fromBytes(request.prePathURL())
  138. def _mod(self, newURL, keepQuery):
  139. """
  140. Return a modified copy of C{self} using C{newURL}, keeping the query
  141. string if C{keepQuery} is C{True}.
  142. @param newURL: a L{URL} to derive a new L{URLPath} from
  143. @type newURL: L{URL}
  144. @param keepQuery: if C{True}, preserve the query parameters from
  145. C{self} on the new L{URLPath}; if C{False}, give the new L{URLPath}
  146. no query parameters.
  147. @type keepQuery: L{bool}
  148. @return: a new L{URLPath}
  149. """
  150. return self._fromURL(
  151. newURL.replace(fragment="", query=self._url.query if keepQuery else ())
  152. )
  153. def sibling(self, path, keepQuery=False):
  154. """
  155. Get the sibling of the current L{URLPath}. A sibling is a file which
  156. is in the same directory as the current file.
  157. @param path: The path of the sibling.
  158. @type path: L{bytes}
  159. @param keepQuery: Whether to keep the query parameters on the returned
  160. L{URLPath}.
  161. @type keepQuery: L{bool}
  162. @return: a new L{URLPath}
  163. """
  164. return self._mod(self._url.sibling(path.decode("ascii")), keepQuery)
  165. def child(self, path, keepQuery=False):
  166. """
  167. Get the child of this L{URLPath}.
  168. @param path: The path of the child.
  169. @type path: L{bytes}
  170. @param keepQuery: Whether to keep the query parameters on the returned
  171. L{URLPath}.
  172. @type keepQuery: L{bool}
  173. @return: a new L{URLPath}
  174. """
  175. return self._mod(self._url.child(path.decode("ascii")), keepQuery)
  176. def parent(self, keepQuery=False):
  177. """
  178. Get the parent directory of this L{URLPath}.
  179. @param keepQuery: Whether to keep the query parameters on the returned
  180. L{URLPath}.
  181. @type keepQuery: L{bool}
  182. @return: a new L{URLPath}
  183. """
  184. return self._mod(self._url.click(".."), keepQuery)
  185. def here(self, keepQuery=False):
  186. """
  187. Get the current directory of this L{URLPath}.
  188. @param keepQuery: Whether to keep the query parameters on the returned
  189. L{URLPath}.
  190. @type keepQuery: L{bool}
  191. @return: a new L{URLPath}
  192. """
  193. return self._mod(self._url.click("."), keepQuery)
  194. def click(self, st):
  195. """
  196. Return a path which is the URL where a browser would presumably take
  197. you if you clicked on a link with an HREF as given.
  198. @param st: A relative URL, to be interpreted relative to C{self} as the
  199. base URL.
  200. @type st: L{bytes}
  201. @return: a new L{URLPath}
  202. """
  203. return self._fromURL(self._url.click(st.decode("ascii")))
  204. def __str__(self) -> str:
  205. """
  206. The L{str} of a L{URLPath} is its URL text.
  207. """
  208. return cast(str, self._url.asURI().asText())
  209. def __repr__(self) -> str:
  210. """
  211. The L{repr} of a L{URLPath} is an eval-able expression which will
  212. construct a similar L{URLPath}.
  213. """
  214. return "URLPath(scheme={!r}, netloc={!r}, path={!r}, query={!r}, fragment={!r})".format(
  215. self.scheme,
  216. self.netloc,
  217. self.path,
  218. self.query,
  219. self.fragment,
  220. )