File size: 12,494 Bytes
a65138c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# -*- coding: utf-8 -*-
from __future__ import with_statement
import logging


logger = logging.getLogger(__name__)


NS_URI = 'http://openoffice.org/extensions/description/2006'
NS_URI_DEP = 'http://openoffice.org/extensions/description/2006'
NS_URI_XLINK = 'http://www.w3.org/1999/xlink'

NS = '{' + NS_URI + '}'
NS_DEP = '{' + NS_URI_DEP + '}'
NS_XLINK = '{' + NS_URI_XLINK + '}'


def as_dict(f):
    def wrapper(*args, **kwargs):
        return dict(f(*args, **kwargs))
    wrapper.items = f
    return wrapper


@as_dict
def get_display_name(doc):
    root = doc.getroot()
    for elt in root.findall(NS + 'display-name/' + NS + 'name'):
        yield elt.get('lang'), elt.text


def set_display_name(doc, display_name):
    import xml.etree.ElementTree as ET
    root = doc.getroot()
    dispname = ET.SubElement(root, 'display-name')
    for lang, name in display_name.items():
        elt = ET.SubElement(dispname, 'name')
        elt.set('lang', lang)
        elt.text = name


@as_dict
def get_extension_description(doc):
    root = doc.getroot()
    for elt in root.findall(NS + 'extension-description/' + NS + 'src'):
        yield elt.get('lang'), elt.get(NS_XLINK + 'href')


def set_extension_description(doc, description):
    import xml.etree.ElementTree as ET
    root = doc.getroot()
    desc = ET.SubElement(root, 'extension-description')
    for lang, url in description.items():
        elt = ET.SubElement(desc, 'src')
        elt.set('lang', lang)
        elt.set('xlink:href', url)


@as_dict
def get_publisher(doc):
    root = doc.getroot()
    for elt in root.findall(NS + 'publisher/' + NS + 'name'):
        yield elt.get('lang'), dict(name=elt.text,
                                    url=elt.get(NS_XLINK + 'href'))


def set_publisher(doc, publisher):
    import xml.etree.ElementTree as ET
    root = doc.getroot()
    pub = ET.SubElement(root, 'publisher')
    for lang, dct in publisher.items():
        elt = ET.SubElement(pub, 'name')
        elt.set('lang', lang)
        elt.set('xlink:href', dct['url'])
        elt.text = dct['name']


def get_license_accept_by(doc):
    root = doc.getroot()
    for elt in root.findall(NS + 'registration/' + NS + 'simple-license'):
        accept_by = elt.get('accept-by')
        if accept_by:
            return accept_by


def get_license(doc):
    root = doc.getroot()
    for elt in root.findall(NS + 'registration/' + NS + 'simple-license'):
        return dict((elt.get('lang'), elt.get(NS_XLINK + 'href'))
                    for elt in elt.findall(NS + 'license-text'))
    return dict()


def get_oo_min_version(doc):
    root = doc.getroot()
    for dep in root.findall(NS + 'dependencies'):
        for elt in dep.findall(NS + 'OpenOffice.org-minimal-version'):
            return elt.get('value')


class Description(object):

    @classmethod
    def parse(cls, f):
        import xml.etree.ElementTree as ET

        doc = ET.parse(f)
        root = doc.getroot()

        def getvalue(xpath, default=None):
            for elt in root.findall(xpath):
                value = elt.get('value', default)
                if value:
                    return value
            return default

        return cls(identifier=getvalue(NS + 'identifier'),
                   version=getvalue(NS + 'version'),
                   platform=getvalue(NS + 'platform', 'all'),
                   display_name=get_display_name(doc),
                   description=get_extension_description(doc),
                   publisher=get_publisher(doc),
                   license_accept_by=get_license_accept_by(doc),
                   license=get_license(doc),
                   oo_min_version=get_oo_min_version(doc))

    def __init__(self,
                 identifier='noname',
                 version='0.0',
                 platform='all',
                 display_name=dict(),
                 description=dict(),
                 publisher=dict(),
                 license_accept_by='admin',
                 license=dict(),
                 oo_min_version=None):
        ''' Generate description.xml

        :param f: output file
        :param identifier: extension identifier
        :param version: extension version
        :param platform: target platform
        :param display_name: localizations of display name
        :param description: localizations of extension description
        :param publisher: localizations of publisher
        :param license_accept_by: who is supposed to accept the license
        :param license: localization of license
        :param oo_min_version: minimal version of LibreOffice

        Each localization parameters are dicts, whose keys are language identifiers
        defined in RFC 3066.

        ``identifier`` specifies `Extension Identifier
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extension_Identifiers>`_.

        ``version`` specifies `Extension Version
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extension_Versions>`_.

        ``platform`` specifies supposed `Target Platform
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Target_Platform>`_  on which this extension
        runs. Default value is ``all``.

        ``display_name`` specifies localized `Display Names
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Display_Name>`_.
        It's a localization dict whose values are localized unicode strings, e.g.::

            display_name = {
                'en': 'Example Filter',
                'ko': u'예제 필터'
            }

        Values of ``description`` is a URL of description file, e.g.::

            description = {
                'en': 'description/en.txt',
                'ko': 'description/ko.txt'
            }

        ``publisher`` specifies `Publisher Information
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Publisher_Information>`_.
        It's a localization dict whose values are dicts themselves, which have
        ``name`` and ``url``.  ``name`` is a localized name of the publisher and
        ``url`` is a URL of the publisher. For example::

            publisher = {
                'en': {
                    'name': 'John Doe',
                    'url': 'http://example.tld'
                },
                'ko': {
                    'name': u'홍길동',
                    'url': 'http://example.tld'
                }
            }

        Optional ``license_accept_by`` specifies who is supposed to accept the
        license. ``admin`` or ``user``. Default value is 'admin'.

        Optional ``license`` is a localization dict whose values are an URL of
        license file. For example::

            license = {
                'en': 'registration/COPYING'
            }

        See `Simple License
        <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Simple_License>`_.
        '''
        self.identifier = identifier
        self.version = version
        self.platform = platform
        self.display_name = display_name
        self.description = description
        self.publisher = publisher
        self.license_accept_by = license_accept_by
        self.license = license
        self.oo_min_version = oo_min_version

    def write(self, f):

        # see http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Description_of_XML_Elements

        import xml.etree.ElementTree as ET

        root = ET.Element('description', {'xmlns': NS_URI,
                                          'xmlns:dep': NS_URI_DEP,
                                          'xmlns:xlink': NS_URI_XLINK})
        doc = ET.ElementTree(root)

        ET.SubElement(root, 'identifier').set('value', self.identifier)
        ET.SubElement(root, 'version').set('value', self.version)
        ET.SubElement(root, 'platform').set('value', self.platform)

        set_display_name(doc, self.display_name)

        set_extension_description(doc, self.description)

        set_publisher(doc, self.publisher)

        if self.license:
            reg = ET.SubElement(root, 'registration')
            lic = ET.SubElement(reg, 'simple-license')
            lic.set('accept-by', self.license_accept_by)
            for lang, url in self.license.items():
                elt = ET.SubElement(lic, 'license-text')
                elt.set('lang', lang)
                elt.set('xlink:href', url)

        if self.oo_min_version is not None:
            dep = ET.SubElement(root, 'dependencies')
            minver = ET.SubElement(dep, 'OpenOffice.org-minimal-version')
            minver.set('dep:name', 'LibreOffice ' + self.oo_min_version)
            minver.set('value', self.oo_min_version)

        f.write('<?xml version="1.0" encoding="utf-8"?>')
        doc.write(f, encoding='utf-8')

    def required_files(self):
        for url in self.description.values():
            yield url
        for url in self.license.values():
            yield url


def print_human_readable(desc, root_stg=None):
    ''' Print summary in human readable form.

    :param desc: an instance of Description
    :param root_stg: root storage of description.xml
    '''
    from storage import resolve_path
    print 'identifier:', desc.identifier
    print 'version:', desc.version
    print 'platform:', desc.platform

    print 'display-name:'
    for lang, name in desc.display_name.items():
        print '  [%s] %s' % (lang, name)

    print 'extension-description:'
    for lang, url in desc.description.items():
        if not root_stg or resolve_path(root_stg, url):
            state = ''
        else:
            state = ' -- MISSING'
        print '  [%s] %s%s' % (lang, url, state)

    print 'publisher:'
    for lang, publisher in desc.publisher.items():
        print '  [%s] %s (%s)' % (lang,
                                       publisher['name'],
                                       publisher['url'])
    if desc.license:
        print 'license: accept-by', desc.license_accept_by
        for lang, url in desc.license.items():
            if not root_stg or resolve_path(root_stg, url):
                state = ''
            else:
                state = ' -- MISSING'
            print '  [%s] %s%s' % (lang, url, state)

    if desc.oo_min_version:
        print 'dependencies:'
        print '  LibreOffice minimal version:', desc.oo_min_version


def init_main():
    doc = '''Usage: oxt-desc-init [options] <desc-file>

    --help      Print this screen.
    '''

    from docopt import docopt
    args = docopt(doc)
    logging.basicConfig(level=logging.INFO)

    description = Description(identifier='tld.example',
                              version='0.1',
                              display_name=dict(en='Example extension'),
                              publisher=dict(en=dict(name='Publisher Name',
                                                     url='http://example.tld')),
                              license=dict(url=dict(en='COPYING')),
                              description=dict(en='description/en.txt'))
    with file(args['<desc-file>'], 'w') as f:
        description.write(f)


def show_main():
    doc = '''Usage: oxt-desc-show [options] <desc-file>

    --help      Show this screen.
    '''
    from docopt import docopt
    args = docopt(doc)
    logging.basicConfig(level=logging.INFO)

    with file(args['<desc-file>']) as f:
        desc = Description.parse(f)

    print_human_readable(desc)


def version_main():
    doc = '''Usage: oxt-desc-version [options] <desc-file> [<new-version>]

    --help      Show this screen.
    '''
    from docopt import docopt
    args = docopt(doc)
    logging.basicConfig(level=logging.INFO)

    with file(args['<desc-file>'], 'r') as f:
        desc = Description.parse(f)

    new_version = args['<new-version>']
    if new_version is not None:
        logger.info('old: %s', desc.version)
        desc.version = new_version
        logger.info('new: %s', desc.version)
        with file(args['<desc-file>'], 'w') as f:
            desc.write(f)
    else:
        print desc.version


def ls_main():
    doc = '''Usage: oxt-desc-ls [options] <desc-file>

    --help      Show this screen.
    '''
    from docopt import docopt
    args = docopt(doc)
    logging.basicConfig(level=logging.INFO)

    with file(args['<desc-file>']) as f:
        desc = Description.parse(f)

    for path in desc.required_files():
        print path