1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/moinformat/input/moindirectory.py Wed Oct 06 00:10:29 2021 +0200
1.3 @@ -0,0 +1,197 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +MoinMoin directory input context.
1.8 +
1.9 +Copyright (C) 2018, 2019, 2021 Paul Boddie <paul@boddie.org.uk>
1.10 +
1.11 +This program is free software; you can redistribute it and/or modify it under
1.12 +the terms of the GNU General Public License as published by the Free Software
1.13 +Foundation; either version 3 of the License, or (at your option) any later
1.14 +version.
1.15 +
1.16 +This program is distributed in the hope that it will be useful, but WITHOUT
1.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.19 +details.
1.20 +
1.21 +You should have received a copy of the GNU General Public License along with
1.22 +this program. If not, see <http://www.gnu.org/licenses/>.
1.23 +"""
1.24 +
1.25 +from moinformat.input.directory import DirectoryInput
1.26 +from moinformat.utils.directory import Directory
1.27 +from os.path import exists, join
1.28 +import re
1.29 +
1.30 +class MoinDirectoryInput(DirectoryInput):
1.31 +
1.32 + "A directory input context."
1.33 +
1.34 + name = "moindirectory"
1.35 +
1.36 + # Pagename and filename translation.
1.37 +
1.38 + unsafe_pagename_characters = re.compile(r"([^A-Za-z0-9_]+)")
1.39 + encoded_pagename_groups = re.compile(r"(\([A-Fa-f0-9]+\))")
1.40 +
1.41 + def __init__(self, metadata):
1.42 +
1.43 + "Initialise the context with the given 'metadata'."
1.44 +
1.45 + if not metadata.has_key("input_filename"):
1.46 + raise ValueError, metadata
1.47 +
1.48 + DirectoryInput.__init__(self, metadata)
1.49 +
1.50 + # Do not search recursively in nested directories for pages.
1.51 + # This overrides the common directory input behaviour.
1.52 +
1.53 + self.nested = False
1.54 +
1.55 + def _get_attachments_dir(self, pagename):
1.56 +
1.57 + "Return the attachments directory for 'pagename'."
1.58 +
1.59 + return self.dir.get_filename(join(self.to_filename(pagename), "attachments"))
1.60 +
1.61 + def _get_current_revision(self, filename):
1.62 +
1.63 + filename = self.dir.get_filename(join(filename, "current"))
1.64 +
1.65 + try:
1.66 + current = open(filename)
1.67 + except IOError:
1.68 + return None
1.69 +
1.70 + try:
1.71 + return current.read().strip()
1.72 + finally:
1.73 + current.close()
1.74 +
1.75 + def all(self):
1.76 +
1.77 + "Return all pages in the context."
1.78 +
1.79 + # Ignore dotfiles. Do not search recursively, and obtain directory names
1.80 + # instead of filenames to identify pages.
1.81 +
1.82 + pages = []
1.83 +
1.84 + for filename in self.dir.select_files("[!.]*", self.nested,
1.85 + directories=True):
1.86 +
1.87 + revision = self._get_current_revision(filename)
1.88 +
1.89 + if revision and exists(self.dir.get_filename(join(filename, "revisions", revision))):
1.90 + pages.append(self.to_pagename(filename))
1.91 +
1.92 + return pages
1.93 +
1.94 + def all_attachments(self):
1.95 +
1.96 + "Return all attachment filenames in the context."
1.97 +
1.98 + return self.dir.select_files(join("*", "attachments", "*"), True)
1.99 +
1.100 + def get_attachments(self, pagename):
1.101 +
1.102 + """
1.103 + Return all attachment filenames for the given 'pagename'. Each filename
1.104 + is relative to the appropriate attachment directory.
1.105 + """
1.106 +
1.107 + return Directory(self._get_attachments_dir(pagename)).select_files("*")
1.108 +
1.109 + # Page characteristics.
1.110 +
1.111 + def subpage_filenames(self, pagename):
1.112 +
1.113 + "Return the subpage filenames of 'pagename'."
1.114 +
1.115 + pattern = self.to_filename("%s%s" % (pagename, self.level_sep))
1.116 + return self.dir.select_files("%s*" % pattern, self.nested, directories=True)
1.117 +
1.118 + # Page access methods.
1.119 +
1.120 + def readfile(self, filename, encoding=None):
1.121 +
1.122 + """
1.123 + Return the contents of the file having the given 'filename' and optional
1.124 + 'encoding'.
1.125 + """
1.126 +
1.127 + # Moin employs a file to indicate the current revision and a directory
1.128 + # containing the revisions.
1.129 +
1.130 + revision = self._get_current_revision(filename)
1.131 +
1.132 + return self.readpath(self.dir.get_filename(join(filename, "revisions",
1.133 + revision)), encoding)
1.134 +
1.135 + # Convenience methods.
1.136 +
1.137 + def get_attachment_filename(self, pagename, filename):
1.138 +
1.139 + """
1.140 + Return the full path of an attachment file for the given 'pagename'
1.141 + having the given 'filename'.
1.142 + """
1.143 +
1.144 + if not pagename:
1.145 + return None
1.146 +
1.147 + return self.dir.get_filename(join(self.to_filename(pagename),
1.148 + "attachments",
1.149 + filename))
1.150 +
1.151 + # NOTE: Translation methods should encode filenames appropriately.
1.152 +
1.153 + def to_filename(self, pagename):
1.154 +
1.155 + "Return the filename corresponding to 'pagename'."
1.156 +
1.157 + # Transform "special" characters into the Moin "(xx)" representation.
1.158 + # Groups of characters are enclosed within each instance.
1.159 +
1.160 + encoded = []
1.161 + append = encoded.append
1.162 + safe = True
1.163 +
1.164 + for group in self.unsafe_pagename_characters.split(pagename):
1.165 + if safe:
1.166 + append(group)
1.167 + else:
1.168 + append("(")
1.169 + for ch in group:
1.170 + append("%02x" % ord(ch))
1.171 + append(")")
1.172 +
1.173 + safe = not safe
1.174 +
1.175 + return DirectoryInput.to_filename(self, "".join(encoded))
1.176 +
1.177 + def to_pagename(self, filename):
1.178 +
1.179 + "Return the pagename corresponding to 'filename'."
1.180 +
1.181 + # Transform "special" characters from the Moin "(xx)" representation.
1.182 +
1.183 + decoded = []
1.184 + append = decoded.append
1.185 + safe = True
1.186 +
1.187 + for group in self.encoded_pagename_groups.split(filename):
1.188 + if safe:
1.189 + append(group)
1.190 + else:
1.191 + num = group[1:-1]
1.192 + append(chr(int(num, 16)))
1.193 +
1.194 + safe = not safe
1.195 +
1.196 + return DirectoryInput.to_pagename(self, "".join(decoded))
1.197 +
1.198 +input = MoinDirectoryInput
1.199 +
1.200 +# vim: tabstop=4 expandtab shiftwidth=4