1.1 --- a/convert.py Sat Aug 04 16:58:51 2018 +0200
1.2 +++ b/convert.py Mon Aug 06 15:55:46 2018 +0200
1.3 @@ -5,6 +5,22 @@
1.4 from os.path import split
1.5 import sys
1.6
1.7 +def getmapping(mappings):
1.8 + mapping = {}
1.9 + key = None
1.10 +
1.11 + for arg in mappings:
1.12 + if key is None:
1.13 + key = arg
1.14 + else:
1.15 + mapping[key] = arg
1.16 + key = None
1.17 +
1.18 + return mapping
1.19 +
1.20 +def getvalue(values):
1.21 + return values and values[0] or None
1.22 +
1.23 def main():
1.24 dirname, progname = split(sys.argv[0])
1.25 args = sys.argv[1:]
1.26 @@ -13,7 +29,10 @@
1.27
1.28 l = filenames = []
1.29 formats = []
1.30 + input_dir_types = []
1.31 + input_dirs = []
1.32 input_encodings = []
1.33 + input_page_seps = []
1.34 mappings = []
1.35 output_dirs = []
1.36 output_encodings = []
1.37 @@ -21,6 +40,7 @@
1.38
1.39 # Flags.
1.40
1.41 + all = False
1.42 macros = False
1.43 tree = False
1.44
1.45 @@ -36,18 +56,41 @@
1.46 elif arg == "--macros":
1.47 macros = True
1.48
1.49 + # Detect all documents.
1.50 +
1.51 + elif arg == "--all":
1.52 + all = True
1.53 +
1.54 # Switch to collecting formats.
1.55
1.56 elif arg == "--format":
1.57 l = formats
1.58 continue
1.59
1.60 + # Switch to collecting input locations.
1.61 +
1.62 + elif arg == "--input-dir":
1.63 + l = input_dirs
1.64 + continue
1.65 +
1.66 + # Switch to collecting input context types.
1.67 +
1.68 + elif arg == "--input-dir-type":
1.69 + l = input_dir_types
1.70 + continue
1.71 +
1.72 # Switch to collecting input encodings.
1.73
1.74 elif arg == "--input-encoding":
1.75 l = input_encodings
1.76 continue
1.77
1.78 + # Switch to collecting input page hierarchy separators.
1.79 +
1.80 + elif arg == "--input-page-sep":
1.81 + l = input_page_seps
1.82 + continue
1.83 +
1.84 # Switch to collecting mappings.
1.85
1.86 elif arg == "--mapping":
1.87 @@ -88,56 +131,88 @@
1.88
1.89 format = formats and formats[0] or "html"
1.90
1.91 - # Derive the page name from the filename if not specified.
1.92 -
1.93 - filename = filenames[0]
1.94 - pagename = pagenames and pagenames[0] or split(filename)[-1]
1.95 -
1.96 # Derive a proper mapping from the given list of values.
1.97
1.98 - mapping = {}
1.99 - key = None
1.100 -
1.101 - for arg in mappings:
1.102 - if key is None:
1.103 - key = arg
1.104 - else:
1.105 - mapping[key] = arg
1.106 - key = None
1.107 -
1.108 - # Obtain output location.
1.109 -
1.110 - output_dir = output_dirs and output_dirs[0] or None
1.111 + mapping = getmapping(mappings)
1.112
1.113 # Obtain encodings.
1.114
1.115 - input_encoding = input_encodings and input_encodings[0] or None
1.116 - output_encoding = output_encodings and output_encodings[0] or None
1.117 + input_encoding = getvalue(input_encodings)
1.118 + output_encoding = getvalue(output_encodings)
1.119 +
1.120 + # Obtain the input and output locations.
1.121 +
1.122 + input_dir = getvalue(input_dirs)
1.123 + output_dir = getvalue(output_dirs)
1.124
1.125 - # Open the file, parse the content, serialise the document.
1.126 + input_page_sep = getvalue(input_page_seps)
1.127 +
1.128 + input_context = input_dir and (getvalue(input_dir_types) or
1.129 + "directory") or "standalone"
1.130 +
1.131 + input = make_input(input_context, {"encoding" : input_encoding,
1.132 + "filename" : input_dir,
1.133 + "separator" : input_page_sep})
1.134
1.135 - input = make_input("standalone", {"encoding" : input_encoding})
1.136 + output_context = output_dir and "directory" or "standalone"
1.137 +
1.138 + output = make_output(output_context, {"encoding" : output_encoding,
1.139 + "filename" : output_dir})
1.140 +
1.141 + # Treat filenames as pagenames if an input directory is indicated and if no
1.142 + # pagenames are explicitly specified.
1.143
1.144 - p = make_parser()
1.145 - d = parse(input.readfile(filename), p)
1.146 + if input_dir:
1.147 + if pagenames:
1.148 + print >>sys.stderr, """\
1.149 +Explicit pagenames (indicated using --pagename) are only to be specified when
1.150 +providing filenames without an input directory (indicated using --input-dir).
1.151
1.152 - if macros:
1.153 - p.evaluate_macros()
1.154 +To indicate pagenames within an input directory, omit any --pagename flags."""
1.155 + sys.exit(1)
1.156
1.157 - # Show a document tree for debugging purposes, if requested.
1.158 + if all:
1.159 + if filenames:
1.160 + print >>sys.stderr, """\
1.161 +Using --all overrides any indicated pagenames. Either --all or the filenames
1.162 +should be omitted."""
1.163 + sys.exit(1)
1.164 + else:
1.165 + filenames = input.all()
1.166
1.167 - if tree:
1.168 - print d.prettyprint()
1.169 + pagenames = filenames
1.170 + filenames = []
1.171 +
1.172 + # Open each file or page, parse the content, serialise the document.
1.173
1.174 - # Otherwise, serialise the document.
1.175 + for pagename, filename in map(None, pagenames, filenames):
1.176 +
1.177 + # Define a pagename if missing.
1.178 +
1.179 + pagename = pagename or split(filename)[-1]
1.180
1.181 - else:
1.182 - # Obtain an output context from any specified output details.
1.183 + # Read either from a filename or using a pagename.
1.184 +
1.185 + if filename:
1.186 + pagetext = input.readfile(filename)
1.187 + else:
1.188 + pagetext = input.readpage(pagename)
1.189 +
1.190 + # Parse the page content.
1.191
1.192 - output_context = output_dir and "directory" or "standalone"
1.193 + p = make_parser()
1.194 + d = parse(pagetext, p)
1.195 +
1.196 + if macros:
1.197 + p.evaluate_macros()
1.198
1.199 - output = make_output(output_context, {"encoding" : output_encoding,
1.200 - "filename" : output_dir})
1.201 + # Show a document tree for debugging purposes, if requested.
1.202 +
1.203 + if tree:
1.204 + print d.prettyprint()
1.205 + continue
1.206 +
1.207 + # Otherwise, serialise the document.
1.208
1.209 # Obtain a linker using format and pagename details.
1.210
1.211 @@ -145,8 +220,17 @@
1.212
1.213 # Obtain a serialiser using the configuration.
1.214
1.215 - serialiser = make_serialiser(format, output, linker)
1.216 - print serialise(d, serialiser)
1.217 + serialiser = make_serialiser(format, output, linker, pagename)
1.218 + outtext = serialise(d, serialiser)
1.219 +
1.220 + # If reading from a file, show the result. Otherwise, write to the
1.221 + # output context.
1.222 +
1.223 + if not output.can_write():
1.224 + print outtext
1.225 + else:
1.226 + output.writepage(outtext, pagename)
1.227 + print >>sys.stderr, pagename
1.228
1.229 if __name__ == "__main__":
1.230 main()
2.1 --- a/moinformat/input/common.py Sat Aug 04 16:58:51 2018 +0200
2.2 +++ b/moinformat/input/common.py Mon Aug 06 15:55:46 2018 +0200
2.3 @@ -19,6 +19,7 @@
2.4 this program. If not, see <http://www.gnu.org/licenses/>.
2.5 """
2.6
2.7 +from os.path import split
2.8 import codecs
2.9
2.10 class Input:
2.11 @@ -31,12 +32,54 @@
2.12
2.13 "Initialise the input context with the optional 'parameters'."
2.14
2.15 - self.parameters = parameters
2.16 - self.encoding = parameters and parameters.get("encoding") or self.default_encoding
2.17 + self.parameters = parameters or {}
2.18 + self.encoding = self.parameters.get("encoding") or self.default_encoding
2.19 +
2.20 + def all(self):
2.21 +
2.22 + "Return all pages in the context."
2.23 +
2.24 + return []
2.25 +
2.26 + # Page characteristics.
2.27 +
2.28 + def parent(self, pagename):
2.29 +
2.30 + "Return the parent of 'pagename'."
2.31 +
2.32 + return "/" in pagename and pagename.rsplit("/", 1)[0] or None
2.33 +
2.34 + def subpages(self, pagename):
2.35 +
2.36 + "Return the subpages of 'pagename'."
2.37 +
2.38 + return []
2.39 +
2.40 + # Page access methods.
2.41
2.42 def readfile(self, filename, encoding=None):
2.43
2.44 """
2.45 + Return the contents of the file having the given 'filename' and optional
2.46 + 'encoding'. This implementation treats 'filename' as a path.
2.47 + """
2.48 +
2.49 + return self.readpath(filename, encoding)
2.50 +
2.51 + def readpage(self, pagename, encoding=None):
2.52 +
2.53 + """
2.54 + Return the contents of the file having the given 'pagename' and optional
2.55 + 'encoding'.
2.56 + """
2.57 +
2.58 + return self.readfile(self.to_filename(pagename), encoding)
2.59 +
2.60 + # Input methods.
2.61 +
2.62 + def readpath(self, filename, encoding=None):
2.63 +
2.64 + """
2.65 Return the contents of the file having the given 'filename'. If the
2.66 optional 'encoding' is specified, override the general encoding.
2.67 """
2.68 @@ -47,4 +90,20 @@
2.69 finally:
2.70 f.close()
2.71
2.72 + # Name translation methods.
2.73 +
2.74 + def to_filename(self, pagename):
2.75 +
2.76 + "Return the filename corresponding to 'pagename'."
2.77 +
2.78 + return pagename
2.79 +
2.80 + def to_pagename(self, filename):
2.81 +
2.82 + "Return the pagename corresponding to 'filename'."
2.83 +
2.84 + # Take the leafname as the pagename from an arbitrary filename.
2.85 +
2.86 + return split(filename)[-1]
2.87 +
2.88 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/moinformat/input/directory.py Sat Aug 04 16:58:51 2018 +0200
3.2 +++ b/moinformat/input/directory.py Mon Aug 06 15:55:46 2018 +0200
3.3 @@ -21,8 +21,9 @@
3.4
3.5 from moinformat.input.common import Input
3.6 from moinformat.utils.directory import Directory
3.7 +from os.path import sep
3.8
3.9 -class DirectoryInput(Input, Directory):
3.10 +class DirectoryInput(Input):
3.11
3.12 "A directory output context."
3.13
3.14 @@ -36,7 +37,36 @@
3.15 raise ValueError, parameters
3.16
3.17 Input.__init__(self, parameters)
3.18 - Directory.__init__(self, parameters["filename"])
3.19 + self.dir = Directory(parameters["filename"])
3.20 +
3.21 + # Support an encoding of the level separator for the filesystem.
3.22 + # Where it is the same as the directory separator, documents are stored
3.23 + # using nested directories, not as a flat list.
3.24 +
3.25 + self.level_sep = parameters and parameters.get("separator") or sep
3.26 +
3.27 + def all(self):
3.28 +
3.29 + "Return all pages in the context."
3.30 +
3.31 + return map(self.to_pagename, self.dir.select_files("*"))
3.32 +
3.33 + # Page characteristics.
3.34 +
3.35 + def subpage_filenames(self, pagename):
3.36 +
3.37 + "Return the subpage filenames of 'pagename'."
3.38 +
3.39 + pattern = self.to_filename("%s/*" % pagename)
3.40 + return self.dir.select_files(pattern)
3.41 +
3.42 + def subpages(self, pagename):
3.43 +
3.44 + "Return the subpages of 'pagename'."
3.45 +
3.46 + return map(self.to_pagename, self.subpage_filenames(pagename))
3.47 +
3.48 + # Page access methods.
3.49
3.50 def readfile(self, filename, encoding=None):
3.51
3.52 @@ -45,7 +75,27 @@
3.53 'encoding'.
3.54 """
3.55
3.56 - return Input.readfile(self, self.get_filename(filename), encoding)
3.57 + return self.readpath(self.dir.get_filename(filename), encoding)
3.58 +
3.59 + # NOTE: Translation methods should encode filenames appropriately.
3.60 +
3.61 + def to_filename(self, pagename):
3.62 +
3.63 + "Return the filename corresponding to 'pagename'."
3.64 +
3.65 + if sep == self.level_sep:
3.66 + return pagename
3.67 + else:
3.68 + return self.level_sep.join(pagename.split("/"))
3.69 +
3.70 + def to_pagename(self, filename):
3.71 +
3.72 + "Return the pagename corresponding to 'filename'."
3.73 +
3.74 + if sep == self.level_sep:
3.75 + return filename
3.76 + else:
3.77 + return "/".join(filename.split(self.level_sep))
3.78
3.79 input = DirectoryInput
3.80
4.1 --- a/moinformat/input/standalone.py Sat Aug 04 16:58:51 2018 +0200
4.2 +++ b/moinformat/input/standalone.py Mon Aug 06 15:55:46 2018 +0200
4.3 @@ -40,15 +40,6 @@
4.4 f = codecs.getreader(self.encoding)(stream)
4.5 return f.read()
4.6
4.7 - def readfile(self, filename, encoding=None):
4.8 -
4.9 - """
4.10 - Return the contents of the file having the given 'filename'. If the
4.11 - optional 'encoding' is specified, override the general encoding.
4.12 - """
4.13 -
4.14 - return Input.readfile(self, filename, encoding)
4.15 -
4.16 input = StandaloneInput
4.17
4.18 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/moinformat/links/html.py Sat Aug 04 16:58:51 2018 +0200
5.2 +++ b/moinformat/links/html.py Mon Aug 06 15:55:46 2018 +0200
5.3 @@ -58,9 +58,14 @@
5.4
5.5 target = target.rstrip("/")
5.6
5.7 + # Fragments.
5.8 +
5.9 + if target.startswith("#"):
5.10 + return self.quote(target), None
5.11 +
5.12 # Sub-pages.
5.13
5.14 - if target.startswith("/"):
5.15 + elif target.startswith("/"):
5.16 return self.translate_subpage(target), None
5.17
5.18 # Sibling (of ancestor) pages.
5.19 @@ -119,8 +124,7 @@
5.20
5.21 "Return a translation of the given attachment 'target'."
5.22
5.23 - return self.quote("%sattachments/%s/%s" % (
5.24 - self.get_top_level(), self.pagename, target))
5.25 + return self.quote("./attachments/%s" % target)
5.26
5.27 def translate_interwiki(self, url, target):
5.28
5.29 @@ -132,7 +136,7 @@
5.30
5.31 "Return a translation of the given relative 'target'."
5.32
5.33 - return self.quote(target[len("../"):])
5.34 + return self.quote(target)
5.35
5.36 def translate_subpage(self, target):
5.37
6.1 --- a/moinformat/output/common.py Sat Aug 04 16:58:51 2018 +0200
6.2 +++ b/moinformat/output/common.py Mon Aug 06 15:55:46 2018 +0200
6.3 @@ -19,6 +19,8 @@
6.4 this program. If not, see <http://www.gnu.org/licenses/>.
6.5 """
6.6
6.7 +import codecs
6.8 +
6.9 class Output:
6.10
6.11 "A common output context abstraction."
6.12 @@ -29,10 +31,13 @@
6.13
6.14 "Initialise the output context with the optional 'parameters'."
6.15
6.16 - self.parameters = parameters
6.17 - self.encoding = parameters and parameters.get("encoding") or self.default_encoding
6.18 + self.parameters = parameters or {}
6.19 + self.encoding = self.parameters.get("encoding") or self.default_encoding
6.20 + self.reset()
6.21
6.22 - # Set up an output collector.
6.23 + def reset(self):
6.24 +
6.25 + "Set up an output collector."
6.26
6.27 self.output = []
6.28
6.29 @@ -48,11 +53,66 @@
6.30
6.31 self.output.append(self.encode(text))
6.32
6.33 + # Page characteristics.
6.34 +
6.35 + def parent(self, pagename):
6.36 +
6.37 + "Return the parent of 'pagename'."
6.38 +
6.39 + return "/" in pagename and pagename.rsplit("/", 1)[0] or None
6.40 +
6.41 + # Serialisation methods.
6.42 +
6.43 def to_string(self):
6.44
6.45 "Return the output as a plain string."
6.46
6.47 - return "".join(self.output)
6.48 + s = "".join(self.output)
6.49 + self.reset()
6.50 + return s
6.51 +
6.52 + # Serialisation methods.
6.53 +
6.54 + def can_write(self):
6.55 +
6.56 + "Return whether this context supports page writing."
6.57 +
6.58 + return False
6.59 +
6.60 + def writefile(self, text, filename, encoding=None):
6.61 +
6.62 + """
6.63 + Write 'text' to the file having the given 'filename'. If the
6.64 + optional 'encoding' is specified, override the general encoding.
6.65 +
6.66 + Subclasses need to override this method for it to have an effect.
6.67 + """
6.68 +
6.69 + pass
6.70 +
6.71 + def writepage(self, text, pagename, encoding=None):
6.72 +
6.73 + """
6.74 + Write 'text' to the file having the given 'pagename' and optional
6.75 + 'encoding'.
6.76 + """
6.77 +
6.78 + return self.writefile(text, self.to_filename(pagename), encoding)
6.79 +
6.80 + # Output methods.
6.81 +
6.82 + def writepath(self, text, filename, encoding=None):
6.83 +
6.84 + """
6.85 + Write 'text' to the file having the given 'filename'. If the
6.86 + optional 'encoding' is specified, override the general encoding.
6.87 + """
6.88 +
6.89 + f = codecs.open(filename, "w", encoding=encoding or self.encoding)
6.90 + try:
6.91 + f.write(text)
6.92 + finally:
6.93 + f.close()
6.94
6.95 def encode(s, encoding):
6.96
7.1 --- a/moinformat/output/directory.py Sat Aug 04 16:58:51 2018 +0200
7.2 +++ b/moinformat/output/directory.py Mon Aug 06 15:55:46 2018 +0200
7.3 @@ -21,8 +21,9 @@
7.4
7.5 from moinformat.output.common import Output
7.6 from moinformat.utils.directory import Directory
7.7 +from os.path import extsep, join
7.8
7.9 -class DirectoryOutput(Output, Directory):
7.10 +class DirectoryOutput(Output):
7.11
7.12 "A directory output context."
7.13
7.14 @@ -36,7 +37,109 @@
7.15 raise ValueError, parameters
7.16
7.17 Output.__init__(self, parameters)
7.18 - Directory.__init__(self, parameters["filename"])
7.19 + self.dir = Directory(parameters["filename"])
7.20 + self.dir.ensure()
7.21 +
7.22 + self.index_name = self.parameters.get("index_name") or "index.html"
7.23 + self.page_suffix = self.parameters.get("page_suffix") or "%shtml" % extsep
7.24 + self.root_pagename = self.parameters.get("root_pagename") or "FrontPage"
7.25 +
7.26 + # Convenience methods.
7.27 +
7.28 + def ensure(self, pagename):
7.29 +
7.30 + "Ensure that the given 'pagename' exists."
7.31 +
7.32 + if not pagename:
7.33 + return None
7.34 +
7.35 + self.dir.ensure(self.to_filename(pagename))
7.36 +
7.37 + def ensure_attachments(self, pagename):
7.38 +
7.39 + "Ensure that attachment storage for the given 'pagename' exists."
7.40 +
7.41 + if not pagename:
7.42 + return None
7.43 +
7.44 + self.dir.ensure(join(self.to_filename(pagename), "attachments"))
7.45 +
7.46 + def get_attachment_filename(self, pagename, filename):
7.47 +
7.48 + """
7.49 + Return the full path of an attachment file for the given 'pagename'
7.50 + having the given 'filename'.
7.51 + """
7.52 +
7.53 + if not pagename:
7.54 + return None
7.55 +
7.56 + return self.dir.get_filename(join(self.to_filename(pagename), "attachments", filename))
7.57 +
7.58 + def get_filename(self, filename):
7.59 +
7.60 + """
7.61 + Return the full path of a file with the given 'filename' found within
7.62 + the directory. The full path is an absolute path.
7.63 + """
7.64 +
7.65 + return self.dir.get_filename(filename)
7.66 +
7.67 + # Name translation methods.
7.68 +
7.69 + def to_filename(self, pagename):
7.70 +
7.71 + "Return the filename corresponding to 'pagename'."
7.72 +
7.73 + # For the root page, use the top-level directory.
7.74 +
7.75 + if pagename == self.root_pagename:
7.76 + return ""
7.77 + else:
7.78 + return pagename
7.79 +
7.80 + def to_pagename(self, filename):
7.81 +
7.82 + "Return the pagename corresponding to 'filename'."
7.83 +
7.84 + return self.within(filename)
7.85 +
7.86 + # Serialisation methods.
7.87 +
7.88 + def can_write(self):
7.89 +
7.90 + "Return whether this context supports page writing."
7.91 +
7.92 + return True
7.93 +
7.94 + def writefile(self, text, filename, encoding=None):
7.95 +
7.96 + """
7.97 + Write 'text' to the file having the given 'filename'. If the
7.98 + optional 'encoding' is specified, override the general encoding.
7.99 + """
7.100 +
7.101 + return self.writepath(text, self.dir.get_filename(filename), encoding)
7.102 +
7.103 + def writepage(self, text, pagename, encoding=None):
7.104 +
7.105 + """
7.106 + Write 'text' to the file having the given 'pagename' and optional
7.107 + 'encoding'. If 'parent' is specified and a true value, it indicates that
7.108 + the page is a parent of other pages.
7.109 + """
7.110 +
7.111 + filename = self.to_filename(pagename)
7.112 +
7.113 + # Make a directory for the page.
7.114 +
7.115 + if not self.dir.exists(filename):
7.116 + self.dir.makedirs(filename)
7.117 +
7.118 + # Write to an index filename within any existing directory.
7.119 +
7.120 + filename = join(filename, self.index_name)
7.121 + self.writefile(text, filename, encoding)
7.122
7.123 output = DirectoryOutput
7.124
8.1 --- a/moinformat/output/standalone.py Sat Aug 04 16:58:51 2018 +0200
8.2 +++ b/moinformat/output/standalone.py Mon Aug 06 15:55:46 2018 +0200
8.3 @@ -27,6 +27,29 @@
8.4
8.5 name = "standalone"
8.6
8.7 + # Convenience methods.
8.8 +
8.9 + def ensure(self, pagename):
8.10 +
8.11 + "Ensure that the given 'pagename' exists."
8.12 +
8.13 + pass
8.14 +
8.15 + def ensure_attachments(self, pagename):
8.16 +
8.17 + "Ensure that attachment storage for the given 'pagename' exists."
8.18 +
8.19 + pass
8.20 +
8.21 + def get_attachment_filename(self, pagename, filename):
8.22 +
8.23 + """
8.24 + Prevent independent output by returning a filename of None corresponding
8.25 + to the given 'pagename' and any specified 'filename'.
8.26 + """
8.27 +
8.28 + return None
8.29 +
8.30 def get_filename(self, filename):
8.31
8.32 """
9.1 --- a/moinformat/serialisers/__init__.py Sat Aug 04 16:58:51 2018 +0200
9.2 +++ b/moinformat/serialisers/__init__.py Mon Aug 06 15:55:46 2018 +0200
9.3 @@ -33,7 +33,7 @@
9.4
9.5 return serialisers["%s.moin" % name]
9.6
9.7 -def make_serialiser(name, output=None, linker=None):
9.8 +def make_serialiser(name, output=None, linker=None, pagename=None):
9.9
9.10 """
9.11 Return a serialiser instance for the format having the given 'name'.
9.12 @@ -43,11 +43,14 @@
9.13
9.14 The optional 'linker' is used to control which linking scheme is used with
9.15 the serialiser, with the default having the same name as the serialiser.
9.16 +
9.17 + The optional 'pagename' indicates the name details of the page to be
9.18 + serialised.
9.19 """
9.20
9.21 output = output or make_output("standalone")
9.22 linker = linker or make_linker(name, "")
9.23 - return get_serialiser(name)(output, serialisers, linker)
9.24 + return get_serialiser(name)(output, serialisers, linker, pagename)
9.25
9.26 def serialise(doc, serialiser=None):
9.27
10.1 --- a/moinformat/serialisers/common.py Sat Aug 04 16:58:51 2018 +0200
10.2 +++ b/moinformat/serialisers/common.py Mon Aug 06 15:55:46 2018 +0200
10.3 @@ -25,17 +25,18 @@
10.4
10.5 format = None # defined by subclasses
10.6
10.7 - def __init__(self, output, formats=None, linker=None):
10.8 + def __init__(self, output, formats=None, linker=None, pagename=None):
10.9
10.10 """
10.11 Initialise the serialiser with an 'output' context, an optional
10.12 - 'formats' mapping from names to serialiser classes, and an optional
10.13 - 'linker' object for translating links.
10.14 + 'formats' mapping from names to serialiser classes, an optional 'linker'
10.15 + object for translating links, and an optional 'pagename'.
10.16 """
10.17
10.18 self.output = output
10.19 self.formats = formats
10.20 self.linker = linker
10.21 + self.pagename = pagename
10.22
10.23 # Initialise a callable for use in serialisation.
10.24
10.25 @@ -52,8 +53,8 @@
10.26 pass
10.27
10.28 def __repr__(self):
10.29 - return "%s(%r, %r, %r)" % (self.__class__.__name__, self.output,
10.30 - self.formats, self.linker)
10.31 + return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.output,
10.32 + self.formats, self.linker, self.pagename)
10.33
10.34 def get_serialiser(self, format):
10.35
10.36 @@ -84,7 +85,7 @@
10.37 if cls is self.__class__:
10.38 return self
10.39 else:
10.40 - return cls(self.output, self.formats, self.linker)
10.41 + return cls(self.output, self.formats, self.linker, self.pagename)
10.42
10.43 def escape_attr(s):
10.44
11.1 --- a/moinformat/serialisers/html/graphviz.py Sat Aug 04 16:58:51 2018 +0200
11.2 +++ b/moinformat/serialisers/html/graphviz.py Mon Aug 06 15:55:46 2018 +0200
11.3 @@ -69,17 +69,17 @@
11.4
11.5 # Special methods for graph production.
11.6
11.7 - def _tag(self, tagname, attrname, filename, attributes, closing):
11.8 - l = ["%s='%s'" % (attrname, escape_attr(filename))]
11.9 + def _tag(self, tagname, attrname, target, attributes, closing):
11.10 + l = ["%s='%s'" % (attrname, escape_attr(target))]
11.11 for key, value in attributes.items():
11.12 l.append("%s='%s'" % (key, value))
11.13 self.out("<%s %s%s>" % (tagname, " ".join(l), closing and " /"))
11.14
11.15 - def image(self, filename, attributes):
11.16 - self._tag("img", "src", filename, attributes, True)
11.17 + def image(self, target, attributes):
11.18 + self._tag("img", "src", target, attributes, True)
11.19
11.20 - def object(self, filename, attributes):
11.21 - self._tag("object", "data", filename, attributes, False)
11.22 + def object(self, target, attributes):
11.23 + self._tag("object", "data", target, attributes, False)
11.24 self.out("</object>")
11.25
11.26 def raw(self, text):
11.27 @@ -97,16 +97,27 @@
11.28 format = self.directives.get("format", ["svg"])[0]
11.29 transforms = self.directives.get("transform", [])
11.30
11.31 + # Graph output is stored for a known page only.
11.32 +
11.33 + if not self.pagename:
11.34 + return
11.35 +
11.36 # Get an identifier and usable filename to store the output.
11.37
11.38 identifier = get_output_identifier(text)
11.39 - filename = self.output.get_filename(identifier)
11.40 + attachment = "%s.%s" % (identifier, format)
11.41 + filename = self.output.get_attachment_filename(self.pagename, attachment)
11.42
11.43 # Handle situations where no independent output is permitted.
11.44
11.45 if not filename:
11.46 return
11.47
11.48 + # Make sure that page attachments can be stored.
11.49 +
11.50 + self.output.ensure_attachments(self.pagename)
11.51 + target, label = self.linker.translate("attachment:%s" % attachment)
11.52 +
11.53 # Permit imagemaps only for image formats.
11.54
11.55 if format in IMAGE_FORMATS:
11.56 @@ -135,12 +146,12 @@
11.57 self.raw(graphviz.get_output())
11.58 attributes["usemap"] = "#%s" % im_attributes["id"]
11.59
11.60 - self.image(filename, attributes)
11.61 + self.image(target, attributes)
11.62
11.63 # For other output, create a file and embed the object.
11.64
11.65 else:
11.66 - self.object(filename, attributes)
11.67 + self.object(target, attributes)
11.68
11.69 serialiser = HTMLGraphvizSerialiser
11.70
12.1 --- a/moinformat/utils/directory.py Sat Aug 04 16:58:51 2018 +0200
12.2 +++ b/moinformat/utils/directory.py Mon Aug 06 15:55:46 2018 +0200
12.3 @@ -19,8 +19,9 @@
12.4 this program. If not, see <http://www.gnu.org/licenses/>.
12.5 """
12.6
12.7 -from glob import glob
12.8 -from os.path import abspath, commonprefix, exists, join
12.9 +from os import makedirs, rename, walk
12.10 +from os.path import abspath, commonprefix, exists, isdir, isfile, join
12.11 +import fnmatch
12.12
12.13 # Get the directory with trailing path separator when assessing path prefixes
12.14 # in order to prevent sibling directory confusion.
12.15 @@ -29,8 +30,8 @@
12.16
12.17 "Return whether 'filename' is inside 'dirname'."
12.18
12.19 - dirname = join(dirname, "")
12.20 - return commonprefix((filename, dirname)) == dirname
12.21 + dirprefix = join(dirname, "")
12.22 + return filename == dirname or commonprefix((filename, dirprefix)) == dirprefix
12.23
12.24 def within(filename, dirname):
12.25
12.26 @@ -56,15 +57,6 @@
12.27
12.28 self.filename = abspath(filename)
12.29
12.30 - def exists(self, filename):
12.31 -
12.32 - """
12.33 - Return whether 'filename' exists within the directory. This filename
12.34 - is relative to the directory.
12.35 - """
12.36 -
12.37 - return exists(self.get_filename(filename))
12.38 -
12.39 def get_filename(self, filename):
12.40
12.41 """
12.42 @@ -81,6 +73,65 @@
12.43 else:
12.44 raise ValueError, filename
12.45
12.46 + # File operations acting on relative filenames.
12.47 +
12.48 + def _apply(self, fn, filename):
12.49 +
12.50 + "Apply 'fn' to the relative 'filename'."
12.51 +
12.52 + return fn(self.get_filename(filename))
12.53 +
12.54 + def ensure(self, filename=None):
12.55 +
12.56 + """
12.57 + Ensure that this directory, or a directory 'filename' within it, exists.
12.58 + """
12.59 +
12.60 + pathname = filename and self.get_filename(filename) or self.filename
12.61 +
12.62 + if not exists(pathname):
12.63 + makedirs(pathname)
12.64 +
12.65 + def exists(self, filename):
12.66 +
12.67 + "Return whether the relative 'filename' exists within the directory."
12.68 +
12.69 + return self._apply(exists, filename)
12.70 +
12.71 + def isdir(self, filename):
12.72 +
12.73 + "Return whether the relative 'filename' is a directory."
12.74 +
12.75 + return self._apply(isdir, filename)
12.76 +
12.77 + def isfile(self, filename):
12.78 +
12.79 + "Return whether the relative 'filename' is a file."
12.80 +
12.81 + return self._apply(isfile, filename)
12.82 +
12.83 + def makedirs(self, filename):
12.84 +
12.85 + """
12.86 + Ensure that a directory having the given 'filename' exists by creating
12.87 + it and any directories needed for it to be created. This filename is
12.88 + relative to the directory.
12.89 + """
12.90 +
12.91 + pathname = self.get_filename(filename)
12.92 +
12.93 + if not exists(pathname):
12.94 + makedirs(pathname)
12.95 +
12.96 + def rename(self, old, new):
12.97 +
12.98 + """
12.99 + Rename the file with the 'old' relative filename to the 'new' relative
12.100 + filename.
12.101 + """
12.102 +
12.103 + rename(self.get_filename(old), self.get_filename(new))
12.104 +
12.105 def select_files(self, pattern):
12.106
12.107 """
12.108 @@ -88,15 +139,38 @@
12.109 'pattern'. These filenames are relative to the directory.
12.110 """
12.111
12.112 - full_pattern = self.get_filename(pattern)
12.113 + selected = []
12.114 +
12.115 + # Obtain pathnames, directory names and filenames within the directory.
12.116 +
12.117 + for dirpath, dirnames, filenames in walk(self.filename):
12.118 + for filename in filenames:
12.119
12.120 - filenames = []
12.121 + # Qualify filenames with the directory path.
12.122 +
12.123 + pathname = join(dirpath, filename)
12.124 +
12.125 + # Obtain the local filename within the directory.
12.126 +
12.127 + local_filename = self.within(pathname)
12.128 +
12.129 + # Match filenames supporting the pattern.
12.130
12.131 - for filename in glob(full_pattern):
12.132 - filename = within(filename, self.filename)
12.133 - if filename:
12.134 - filenames.append(filename)
12.135 + if local_filename and fnmatch.fnmatch(local_filename, pattern):
12.136 + selected.append(local_filename)
12.137 +
12.138 + return selected
12.139 +
12.140 + # File operations involving complete filenames.
12.141
12.142 - return filenames
12.143 + def within(self, filename):
12.144 +
12.145 + """
12.146 + Return the given complete 'filename' translated to be relative to this
12.147 + directory, or return None if the filename describes a location outside
12.148 + the directory.
12.149 + """
12.150 +
12.151 + return within(filename, self.filename)
12.152
12.153 # vim: tabstop=4 expandtab shiftwidth=4
13.1 --- a/tests/test_parser.py Sat Aug 04 16:58:51 2018 +0200
13.2 +++ b/tests/test_parser.py Mon Aug 06 15:55:46 2018 +0200
13.3 @@ -183,7 +183,7 @@
13.4
13.5 # Obtain input filenames.
13.6
13.7 - filenames = args or input.select_files("test*.txt*")
13.8 + filenames = args or input.dir.select_files("test*.txt*")
13.9 filenames.sort()
13.10
13.11 # Process each filename, obtaining a corresponding tree definition.
13.12 @@ -209,7 +209,7 @@
13.13
13.14 # Read and parse any tree definition.
13.15
13.16 - if input.exists(tree_filename):
13.17 + if input.dir.exists(tree_filename):
13.18 ts = input.readfile(tree_filename)
13.19 t = parse_tree(ts)
13.20 else: