1 #!/usr/bin/env python 2 3 """ 4 MoinMoin directory input context. 5 6 Copyright (C) 2018, 2019, 2021 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from moinformat.input.directory import DirectoryInput 23 from moinformat.utils.directory import Directory 24 from os.path import exists, join 25 import re 26 27 class MoinDirectoryInput(DirectoryInput): 28 29 "A directory input context." 30 31 name = "moindirectory" 32 33 # Pagename and filename translation. 34 35 unsafe_pagename_characters = re.compile(r"([^A-Za-z0-9_]+)") 36 encoded_pagename_groups = re.compile(r"(\([A-Fa-f0-9]+\))") 37 38 def __init__(self, metadata): 39 40 "Initialise the context with the given 'metadata'." 41 42 if not metadata.has_key("input_filename"): 43 raise ValueError, metadata 44 45 DirectoryInput.__init__(self, metadata) 46 47 # Do not search recursively in nested directories for pages. 48 # This overrides the common directory input behaviour. 49 50 self.nested = False 51 52 def _get_attachments_dir(self, pagename): 53 54 "Return the attachments directory for 'pagename'." 55 56 return self.dir.get_filename(join(self.to_filename(pagename), "attachments")) 57 58 def _get_current_revision(self, filename): 59 60 filename = self.dir.get_filename(join(filename, "current")) 61 62 try: 63 current = open(filename) 64 except IOError: 65 return None 66 67 try: 68 return current.read().strip() 69 finally: 70 current.close() 71 72 def all(self): 73 74 "Return all pages in the context." 75 76 # Ignore dotfiles. Do not search recursively, and obtain directory names 77 # instead of filenames to identify pages. 78 79 pages = [] 80 81 for filename in self.dir.select_files("[!.]*", self.nested, 82 directories=True): 83 84 revision = self._get_current_revision(filename) 85 86 if revision and exists(self.dir.get_filename(join(filename, "revisions", revision))): 87 pages.append(self.to_pagename(filename)) 88 89 return pages 90 91 def all_attachments(self): 92 93 "Return all attachment filenames in the context." 94 95 return self.dir.select_files(join("*", "attachments", "*"), True) 96 97 def get_attachments(self, pagename): 98 99 """ 100 Return all attachment filenames for the given 'pagename'. Each filename 101 is relative to the appropriate attachment directory. 102 """ 103 104 return Directory(self._get_attachments_dir(pagename)).select_files("*") 105 106 # Page characteristics. 107 108 def subpage_filenames(self, pagename): 109 110 "Return the subpage filenames of 'pagename'." 111 112 pattern = self.to_filename("%s%s" % (pagename, self.level_sep)) 113 return self.dir.select_files("%s*" % pattern, self.nested, directories=True) 114 115 # Page access methods. 116 117 def readfile(self, filename, encoding=None): 118 119 """ 120 Return the contents of the file having the given 'filename' and optional 121 'encoding'. 122 """ 123 124 # Moin employs a file to indicate the current revision and a directory 125 # containing the revisions. 126 127 revision = self._get_current_revision(filename) 128 129 return self.readpath(self.dir.get_filename(join(filename, "revisions", 130 revision)), encoding) 131 132 # Convenience methods. 133 134 def get_attachment_filename(self, pagename, filename): 135 136 """ 137 Return the full path of an attachment file for the given 'pagename' 138 having the given 'filename'. 139 """ 140 141 if not pagename: 142 return None 143 144 return self.dir.get_filename(join(self.to_filename(pagename), 145 "attachments", 146 filename)) 147 148 # NOTE: Translation methods should encode filenames appropriately. 149 150 def to_filename(self, pagename): 151 152 "Return the filename corresponding to 'pagename'." 153 154 # Transform "special" characters into the Moin "(xx)" representation. 155 # Groups of characters are enclosed within each instance. 156 157 encoded = [] 158 append = encoded.append 159 safe = True 160 161 for group in self.unsafe_pagename_characters.split(pagename): 162 if safe: 163 append(group) 164 else: 165 append("(") 166 for ch in group: 167 append("%02x" % ord(ch)) 168 append(")") 169 170 safe = not safe 171 172 return DirectoryInput.to_filename(self, "".join(encoded)) 173 174 def to_pagename(self, filename): 175 176 "Return the pagename corresponding to 'filename'." 177 178 # Transform "special" characters from the Moin "(xx)" representation. 179 180 decoded = [] 181 append = decoded.append 182 safe = True 183 184 for group in self.encoded_pagename_groups.split(filename): 185 if safe: 186 append(group) 187 else: 188 num = group[1:-1] 189 append(chr(int(num, 16))) 190 191 safe = not safe 192 193 return DirectoryInput.to_pagename(self, "".join(decoded)) 194 195 input = MoinDirectoryInput 196 197 # vim: tabstop=4 expandtab shiftwidth=4