Git based wiki inspired by Gollum
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.

206 lines
4.1KB

  1. from __future__ import absolute_import
  2. import re
  3. import os
  4. import hashlib
  5. import json
  6. import string
  7. import random
  8. import sys
  9. import click
  10. from jinja2 import Template
  11. class AttrDict(dict):
  12. def __init__(self, *args, **kwargs):
  13. super(AttrDict, self).__init__(*args, **kwargs)
  14. self.__dict__ = self
  15. def random_string(size=6, chars=string.ascii_lowercase + string.ascii_uppercase + string.digits):
  16. return ''.join(random.choice(chars) for _ in range(size))
  17. def to_json(data):
  18. return json.dumps(to_dict(data), separators=(',', ':'))
  19. def to_dict(data):
  20. if not data:
  21. return AttrDict()
  22. def row2dict(row):
  23. d = AttrDict()
  24. for column in row.__table__.columns:
  25. d[column.name] = getattr(row, column.name)
  26. return d
  27. if isinstance(data, list):
  28. return [row2dict(x) for x in data]
  29. else:
  30. return row2dict(data)
  31. def mkdir_safe(path):
  32. if path and not(os.path.exists(path)):
  33. os.makedirs(path)
  34. return path
  35. def extract_path(file_path):
  36. if not file_path:
  37. return None
  38. last_slash = file_path.rindex("/")
  39. if last_slash:
  40. return file_path[0, last_slash]
  41. def clean_path(path):
  42. if path:
  43. if path[0] != '/':
  44. path.insert(0, '/')
  45. return re.sub(r"//+", '/', path)
  46. def extract_name(file_path):
  47. if file_path[-1] == "/":
  48. return None
  49. return os.path.basename(file_path)
  50. def remove_ext(path):
  51. return re.sub(r"\..*$", "", path)
  52. def clean_url(url):
  53. if not url:
  54. return url
  55. url = url.replace('%2F', '/')
  56. url = re.sub(r"^/+", "", url)
  57. return re.sub(r"//+", '/', url)
  58. def to_canonical(s):
  59. """
  60. Remove leading/trailing whitespace (from all path components)
  61. Remove leading underscores and slashes "/"
  62. Convert spaces to dashes "-"
  63. Limit path components to 63 chars and total size to 436 chars
  64. """
  65. reserved_chars = "&$+,:;=?@#"
  66. unsafe_chars = "?<>[]{}|\^~%"
  67. s = s.encode("utf8")
  68. s = re.sub(r"\s+", " ", s)
  69. s = s.lstrip("_/ ")
  70. s = re.sub(r"[" + re.escape(reserved_chars) + "]", "", s)
  71. s = re.sub(r"[" + re.escape(unsafe_chars) + "]", "", s)
  72. # Strip leading/trailing spaces from path components, replace internal spaces
  73. # with '-', and truncate to 63 characters.
  74. parts = (part.strip().replace(" ", "-")[:63] for part in s.split("/"))
  75. # Join any non-empty path components back together
  76. s = "/".join(filter(None, parts))
  77. s = s[:436]
  78. return s
  79. def cname_to_filename(cname):
  80. """ Convert canonical name to filename
  81. :param cname: Canonical name
  82. :return: str -- Filename
  83. """
  84. return cname + ".md"
  85. def filename_to_cname(filename):
  86. """Convert filename to canonical name.
  87. .. note::
  88. It's assumed filename is already canonical format
  89. """
  90. return os.path.splitext(filename)[0]
  91. def gravatar_url(email):
  92. email = hashlib.md5(email).hexdigest() if email else "default@realms.io"
  93. return "https://www.gravatar.com/avatar/" + email
  94. def in_virtualenv():
  95. return hasattr(sys, 'real_prefix')
  96. def in_vagrant():
  97. return os.path.isdir("/vagrant")
  98. def is_su():
  99. return os.geteuid() == 0
  100. def green(s):
  101. click.secho(s, fg='green')
  102. def yellow(s):
  103. click.secho(s, fg='yellow')
  104. def red(s):
  105. click.secho(s, fg='red')
  106. def upstart_script(user='root', app_dir=None, port=5000, workers=2, path=None):
  107. script = """
  108. limit nofile 65335 65335
  109. respawn
  110. description "Realms Wiki"
  111. author "scragg@gmail.com"
  112. chdir {{ app_dir }}
  113. {% if path %}
  114. env PATH={{ path }}:/usr/local/bin:/usr/bin:/bin:$PATH
  115. export PATH
  116. {% endif %}
  117. env LC_ALL=en_US.UTF-8
  118. env GEVENT_RESOLVER=ares
  119. export LC_ALL
  120. export GEVENT_RESOLVER
  121. setuid {{ user }}
  122. setgid {{ user }}
  123. start on runlevel [2345]
  124. stop on runlevel [!2345]
  125. respawn
  126. exec gunicorn \
  127. --name realms-wiki \
  128. --access-logfile - \
  129. --error-logfile - \
  130. --worker-class gevent \
  131. --workers {{ workers }} \
  132. --bind 0.0.0.0:{{ port }} \
  133. --user {{ user }} \
  134. --group {{ user }} \
  135. --chdir {{ app_dir }} \
  136. 'realms:create_app()'
  137. """
  138. template = Template(script)
  139. return template.render(user=user, app_dir=app_dir, port=port, workers=workers, path=path)