style
[librarian.git] / librarian / book2anything.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
5 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
6 #
7 import os.path
8 import optparse
9
10 from librarian import DirDocProvider, ParseError
11 from librarian.parser import WLDocument
12
13
14 class Option(object):
15     """Option for optparse. Use it like `optparse.OptionParser.add_option`."""
16     def __init__(self, *names, **options):
17         self.names = names
18         self.options = options
19
20     def add(self, parser):
21         parser.add_option(*self.names, **self.options)
22
23     def name(self):
24         return self.options['dest']
25
26     def value(self, options):
27         return getattr(options, self.name())
28
29
30 class Book2Anything(object):
31     """A class for creating book2... scripts.
32
33     Subclass it for any format you want to convert to.
34     """
35     format_name = None  # Set format name, like "PDF".
36     ext = None  # Set file extension, like "pdf".
37     uses_cover = False  # Can it add a cover?
38     cover_optional = True  # Only relevant if uses_cover
39     uses_provider = False  # Does it need a DocProvider?
40     transform = None  # Transform method. Uses WLDocument.as_{ext} by default.
41     parser_options = []  # List of Option objects for additional parser args.
42     transform_options = []  # List of Option objects for additional transform args.
43     transform_flags = []  # List of Option objects for supported transform flags.
44
45     @classmethod
46     def run(cls):
47         # Parse commandline arguments
48         usage = """Usage: %%prog [options] SOURCE [SOURCE...]
49         Convert SOURCE files to %s format.""" % cls.format_name
50
51         parser = optparse.OptionParser(usage=usage)
52
53         parser.add_option(
54             '-v', '--verbose',
55             action='store_true', dest='verbose', default=False,
56             help='print status messages to stdout')
57         parser.add_option(
58             '-d', '--make-dir',
59             action='store_true', dest='make_dir', default=False,
60             help='create a directory for author and put the output file in it')
61         parser.add_option(
62             '-o', '--output-file',
63             dest='output_file', metavar='FILE',
64             help='specifies the output file')
65         parser.add_option(
66             '-O', '--output-dir',
67             dest='output_dir', metavar='DIR',
68             help='specifies the directory for output')
69         if cls.uses_cover:
70             if cls.cover_optional:
71                 parser.add_option(
72                     '-c', '--with-cover',
73                     action='store_true', dest='with_cover', default=False,
74                     help='create default cover')
75             parser.add_option(
76                 '-C', '--image-cache',
77                 dest='image_cache', metavar='URL',
78                 help='prefix for image download cache' +
79                      (' (implies --with-cover)' if cls.cover_optional else ''))
80         for option in cls.parser_options + cls.transform_options + cls.transform_flags:
81             option.add(parser)
82
83         options, input_filenames = parser.parse_args()
84
85         if len(input_filenames) < 1:
86             parser.print_help()
87             return 1
88
89         # Prepare additional args for parser.
90         parser_args = {}
91         for option in cls.parser_options:
92             parser_args[option.name()] = option.value(options)
93         # Prepare additional args for transform method.
94         transform_args = {"verbose": options.verbose}
95         for option in cls.transform_options:
96             transform_args[option.name()] = option.value(options)
97         # Add flags to transform_args, if any.
98         transform_flags = [flag.name() for flag in cls.transform_flags if flag.value(options)]
99         if transform_flags:
100             transform_args['flags'] = transform_flags
101         # Add cover support, if any.
102         if cls.uses_cover:
103             from librarian.styles.wolnelektury.cover import WLCover
104             if options.image_cache:
105                 def cover_class(*args, **kwargs):
106                     return WLCover(image_cache=options.image_cache, *args, **kwargs)
107                 transform_args['cover'] = cover_class
108             elif not cls.cover_optional or options.with_cover:
109                 transform_args['cover'] = WLCover
110
111         # Do some real work
112         main_input = None
113         try:
114             for main_input in input_filenames:
115                 if options.verbose:
116                     print main_input
117
118                 # Where to find input?
119                 if cls.uses_provider:
120                     path, fname = os.path.realpath(main_input).rsplit('/', 1)
121                     provider = DirDocProvider(path)
122                 else:
123                     provider = None
124
125                 # Where to write output?
126                 if not (options.output_file or options.output_dir):
127                     output_file = os.path.splitext(main_input)[0] + '.' + cls.ext
128                 else:
129                     output_file = None
130
131                 # Do the transformation.
132                 doc = WLDocument.from_file(main_input, provider=provider, **parser_args)
133                 transform = cls.transform
134                 if transform is None:
135                     transform = getattr(WLDocument, 'as_%s' % cls.ext)
136                 output = transform(doc, **transform_args)
137
138                 doc.save_output_file(
139                     output, output_file, options.output_dir, options.make_dir, cls.ext)
140
141         except ParseError, e:
142             print '%(file)s:%(name)s:%(message)s' % {
143                 'file': main_input,
144                 'name': e.__class__.__name__,
145                 'message': e
146             }