source: branches/4.x/buildhtml.py @ 587

Last change on this file since 587 was 521, checked in by darcy, 4 years ago

Use hard coded path rather than env.

  • Property svn:executable set to *
  • Property svn:keywords set to
    Revision
    Date
File size: 8.4 KB
Line 
1#! /usr/bin/python
2
3# Author: David Goodger
4# Contact: goodger@users.sourceforge.net
5# Revision: $Revision: 521 $
6# Date: $Date: 2015-07-19 00:57:59 +0000 (Sun, 19 Jul 2015) $
7# Copyright: This module has been placed in the public domain.
8
9"""
10Generates .html from all the .txt files in a directory.
11
12Ordinary .txt files are understood to be standalone reStructuredText.
13Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs.
14"""
15# Once PySource is here, build .html from .py as well.
16
17__docformat__ = 'reStructuredText'
18
19
20try:
21    import locale
22    locale.setlocale(locale.LC_ALL, '')
23except:
24    pass
25
26import sys
27import os
28import os.path
29import copy
30import docutils
31from docutils import ApplicationError
32from docutils import core, frontend
33from docutils.parsers import rst
34from docutils.readers import standalone, pep
35from docutils.writers import html4css1, pep_html
36
37
38usage = '%prog [options] [<directory> ...]'
39description = ('Generates .html from all the reStructuredText .txt files '
40               '(including PEPs) in each <directory> '
41               '(default is the current directory).')
42
43
44class SettingsSpec(docutils.SettingsSpec):
45
46    """
47    Runtime settings & command-line options for the front end.
48    """
49
50    # Can't be included in OptionParser below because we don't want to
51    # override the base class.
52    settings_spec = (
53        'Build-HTML Options',
54        None,
55        (('Recursively scan subdirectories for files to process.  This is '
56          'the default.',
57          ['--recurse'],
58          {'action': 'store_true', 'default': 1,
59           'validator': frontend.validate_boolean}),
60         ('Do not scan subdirectories for files to process.',
61          ['--local'], {'dest': 'recurse', 'action': 'store_false'}),
62         ('Do not process files in <directory>.  This option may be used '
63          'more than once to specify multiple directories.',
64          ['--prune'],
65          {'metavar': '<directory>', 'action': 'append',
66           'validator': frontend.validate_colon_separated_string_list}),
67         ('Work silently (no progress messages).  Independent of "--quiet".',
68          ['--silent'],
69          {'action': 'store_true', 'validator': frontend.validate_boolean}),))
70
71    relative_path_settings = ('prune',)
72    config_section = 'buildhtml application'
73    config_section_dependencies = ('applications',)
74
75
76class OptionParser(frontend.OptionParser):
77
78    """
79    Command-line option processing for the ``buildhtml.py`` front end.
80    """
81
82    def check_values(self, values, args):
83        frontend.OptionParser.check_values(self, values, args)
84        values._source = None
85        return values
86
87    def check_args(self, args):
88        source = destination = None
89        if args:
90            self.values._directories = args
91        else:
92            self.values._directories = [os.getcwd()]
93        return source, destination
94
95
96class Struct:
97
98    """Stores data attributes for dotted-attribute access."""
99
100    def __init__(self, **keywordargs):
101        self.__dict__.update(keywordargs)
102
103
104class Builder:
105
106    def __init__(self):
107        self.publishers = {
108            '': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer,
109                                   SettingsSpec)),
110            '.txt': Struct(components=(rst.Parser, standalone.Reader,
111                                       html4css1.Writer, SettingsSpec),
112                           reader_name='standalone',
113                           writer_name='html'),
114            'PEPs': Struct(components=(rst.Parser, pep.Reader,
115                                       pep_html.Writer, SettingsSpec),
116                           reader_name='pep',
117                           writer_name='pep_html')}
118        """Publisher-specific settings.  Key '' is for the front-end script
119        itself.  ``self.publishers[''].components`` must contain a superset of
120        all components used by individual publishers."""
121
122        self.setup_publishers()
123
124    def setup_publishers(self):
125        """
126        Manage configurations for individual publishers.
127
128        Each publisher (combination of parser, reader, and writer) may have
129        its own configuration defaults, which must be kept separate from those
130        of the other publishers.  Setting defaults are combined with the
131        config file settings and command-line options by
132        `self.get_settings()`.
133        """
134        for name, publisher in self.publishers.items():
135            option_parser = OptionParser(
136                components=publisher.components, read_config_files=1,
137                usage=usage, description=description)
138            publisher.option_parser = option_parser
139            publisher.setting_defaults = option_parser.get_default_values()
140            frontend.make_paths_absolute(publisher.setting_defaults.__dict__,
141                                         option_parser.relative_path_settings)
142            publisher.config_settings = (
143                option_parser.get_standard_config_settings())
144        self.settings_spec = self.publishers[''].option_parser.parse_args(
145            values=frontend.Values())   # no defaults; just the cmdline opts
146        self.initial_settings = self.get_settings('')
147
148    def get_settings(self, publisher_name, directory=None):
149        """
150        Return a settings object, from multiple sources.
151
152        Copy the setting defaults, overlay the startup config file settings,
153        then the local config file settings, then the command-line options.
154        Assumes the current directory has been set.
155        """
156        publisher = self.publishers[publisher_name]
157        settings = frontend.Values(publisher.setting_defaults.__dict__)
158        settings.update(publisher.config_settings, publisher.option_parser)
159        if directory:
160            local_config = publisher.option_parser.get_config_file_settings(
161                os.path.join(directory, 'docutils.conf'))
162            frontend.make_paths_absolute(
163                local_config, publisher.option_parser.relative_path_settings,
164                directory)
165            settings.update(local_config, publisher.option_parser)
166        settings.update(self.settings_spec.__dict__, publisher.option_parser)
167        return settings
168
169    def run(self, directory=None, recurse=1):
170        recurse = recurse and self.initial_settings.recurse
171        if directory:
172            self.directories = [directory]
173        elif self.settings_spec._directories:
174            self.directories = self.settings_spec._directories
175        else:
176            self.directories = [os.getcwd()]
177        for directory in self.directories:
178            os.path.walk(directory, self.visit, recurse)
179
180    def visit(self, recurse, directory, names):
181        settings = self.get_settings('', directory)
182        if settings.prune and (os.path.abspath(directory) in settings.prune):
183            print >>sys.stderr, '/// ...Skipping directory (pruned):', directory
184            sys.stderr.flush()
185            names[:] = []
186            return
187        if not self.initial_settings.silent:
188            print >>sys.stderr, '/// Processing directory:', directory
189            sys.stderr.flush()
190        prune = 0
191        for name in names:
192            if name.endswith('.txt'):
193                prune = self.process_txt(directory, name)
194                if prune:
195                    break
196        if not recurse:
197            del names[:]
198
199    def process_txt(self, directory, name):
200        if name.startswith('pep-'):
201            publisher = 'PEPs'
202        else:
203            publisher = '.txt'
204        settings = self.get_settings(publisher, directory)
205        pub_struct = self.publishers[publisher]
206        if settings.prune and (directory in settings.prune):
207            return 1
208        settings._source = os.path.normpath(os.path.join(directory, name))
209        settings._destination = settings._source[:-4]+'.html'
210        if not self.initial_settings.silent:
211            print >>sys.stderr, '    ::: Processing:', name
212            sys.stderr.flush()
213        try:
214            core.publish_file(source_path=settings._source,
215                              destination_path=settings._destination,
216                              reader_name=pub_struct.reader_name,
217                              parser_name='restructuredtext',
218                              writer_name=pub_struct.writer_name,
219                              settings=settings)
220        except ApplicationError, error:
221            print >>sys.stderr, ('        Error (%s): %s'
222                                 % (error.__class__.__name__, error))
223
224
225if __name__ == "__main__":
226    Builder().run()
Note: See TracBrowser for help on using the repository browser.