|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +############################################################################### |
| 3 | +# |
| 4 | +# Copyright (C) 2014 Tom Kralidis (tomkralidis@gmail.com) |
| 5 | +# |
| 6 | +# This source is free software; you can redistribute it and/or modify it under |
| 7 | +# the terms of the GNU General Public License as published by the Free |
| 8 | +# Software Foundation; either version 2 of the License, or (at your option) |
| 9 | +# any later version. |
| 10 | +# |
| 11 | +# This code is distributed in the hope that it will be useful, but WITHOUT ANY |
| 12 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 13 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| 14 | +# details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU General Public License along |
| 17 | +# with this program; if not, write to the Free Software Foundation, Inc., |
| 18 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 19 | +# |
| 20 | +############################################################################### |
| 21 | + |
| 22 | +import getpass |
| 23 | +import os |
| 24 | +import shutil |
| 25 | +from urllib import urlencode |
| 26 | +from urllib2 import urlopen |
| 27 | +import xml.etree.ElementTree as etree |
| 28 | +import xmlrpclib |
| 29 | +import zipfile |
| 30 | + |
| 31 | +from jinja2 import Environment, FileSystemLoader |
| 32 | +from paver.easy import (call_task, cmdopts, error, info, needs, options, path, |
| 33 | + pushd, sh, task, Bunch) |
| 34 | + |
| 35 | +PLUGIN_NAME = 'MetaSearch' |
| 36 | +BASEDIR = os.path.abspath(os.path.dirname(__file__)) |
| 37 | +USERDIR = os.path.expanduser('~') |
| 38 | + |
| 39 | +options( |
| 40 | + base=Bunch( |
| 41 | + home=BASEDIR, |
| 42 | + docs=path(BASEDIR) / 'docs', |
| 43 | + plugin=path('%s/plugin/MetaSearch' % BASEDIR), |
| 44 | + ui=path(BASEDIR) / 'plugin' / PLUGIN_NAME / 'ui', |
| 45 | + install=path('%s/.qgis2/python/plugins/MetaSearch' % USERDIR), |
| 46 | + ext_libs=path('plugin/MetaSearch/ext-libs'), |
| 47 | + tmp=path(path('%s/MetaSearch-dist' % USERDIR)), |
| 48 | + version=open('VERSION.txt').read().strip() |
| 49 | + ), |
| 50 | + upload=Bunch( |
| 51 | + host='plugins.qgis.org', |
| 52 | + port=80, |
| 53 | + endpoint='plugins/RPC2/' |
| 54 | + ) |
| 55 | +) |
| 56 | + |
| 57 | + |
| 58 | +@task |
| 59 | +def setup(): |
| 60 | + """setup plugin dependencies""" |
| 61 | + |
| 62 | + if not os.path.exists(options.base.ext_libs): |
| 63 | + sh('pip install -r requirements.txt --target=%s' % |
| 64 | + options.base.ext_libs) |
| 65 | + |
| 66 | + |
| 67 | +@task |
| 68 | +def clean(): |
| 69 | + """clean environment""" |
| 70 | + |
| 71 | + if os.path.exists(options.base.install): |
| 72 | + if os.path.islink(options.base.install): |
| 73 | + os.unlink(options.base.install) |
| 74 | + else: |
| 75 | + shutil.rmtree(options.base.install) |
| 76 | + if os.path.exists(options.base.tmp): |
| 77 | + shutil.rmtree(options.base.tmp) |
| 78 | + if os.path.exists(options.base.ext_libs): |
| 79 | + shutil.rmtree(options.base.ext_libs) |
| 80 | + with pushd(options.base.docs): |
| 81 | + sh('%s clean' % sphinx_make()) |
| 82 | + for ui_file in os.listdir(options.base.ui): |
| 83 | + if ui_file.endswith('.py') and ui_file != '__init__.py': |
| 84 | + os.remove(options.base.plugin / 'ui' / ui_file) |
| 85 | + os.remove(path(options.base.home) / '%s.pro' % PLUGIN_NAME) |
| 86 | + sh('git clean -dxf') |
| 87 | + |
| 88 | + |
| 89 | +@task |
| 90 | +def build_ui_files(): |
| 91 | + """build ui files""" |
| 92 | + |
| 93 | + # compile .ui files into Python |
| 94 | + for ui_file in os.listdir(options.base.ui): |
| 95 | + if ui_file.endswith('.ui'): |
| 96 | + ui_file_basename = os.path.splitext(ui_file)[0] |
| 97 | + sh('pyuic4 -o %s/ui/%s.py %s/ui/%s.ui' % (options.base.plugin, |
| 98 | + ui_file_basename, options.base.plugin, ui_file_basename)) |
| 99 | + |
| 100 | + |
| 101 | +@task |
| 102 | +def build_pro_file(): |
| 103 | + """create .pro file""" |
| 104 | + |
| 105 | + get_translations() |
| 106 | + |
| 107 | + pyfiles = [] |
| 108 | + uifiles = [] |
| 109 | + trfiles = [] |
| 110 | + |
| 111 | + for root, dirs, files in os.walk(options.base.plugin / 'dialogs'): |
| 112 | + for pfile in files: |
| 113 | + if pfile.endswith('.py'): |
| 114 | + filepath = os.path.join(root, pfile) |
| 115 | + relpath = os.path.relpath(filepath, BASEDIR) |
| 116 | + pyfiles.append(relpath) |
| 117 | + pyfiles.append(options.base.plugin / 'plugin.py') |
| 118 | + |
| 119 | + for root, dirs, files in os.walk(options.base.plugin / 'ui'): |
| 120 | + for pfile in files: |
| 121 | + if pfile.endswith('.ui'): |
| 122 | + filepath = os.path.join(root, pfile) |
| 123 | + relpath = os.path.relpath(filepath, BASEDIR) |
| 124 | + uifiles.append(relpath) |
| 125 | + |
| 126 | + locale_dir = options.base.plugin / 'locale' |
| 127 | + for loc_dir in os.listdir(locale_dir): |
| 128 | + filepath = os.path.join(locale_dir, loc_dir, 'LC_MESSAGES', 'ui.ts') |
| 129 | + relpath = os.path.relpath(filepath, BASEDIR) |
| 130 | + trfiles.append(relpath) |
| 131 | + |
| 132 | + with open('%s.pro' % PLUGIN_NAME, 'w') as pro_file: |
| 133 | + pro_file.write('SOURCES=%s\n' % ' '.join(pyfiles)) |
| 134 | + pro_file.write('FORMS=%s\n' % ' '.join(uifiles)) |
| 135 | + pro_file.write('TRANSLATIONS=%s\n' % ' '.join(trfiles)) |
| 136 | + |
| 137 | + |
| 138 | +@task |
| 139 | +@needs(['build_pro_file']) |
| 140 | +def extract_messages(): |
| 141 | + """generate .pot/.ts files from sources""" |
| 142 | + |
| 143 | + # generate UI .ts file |
| 144 | + sh('pylupdate4 -noobsolete MetaSearch.pro') |
| 145 | + |
| 146 | + # generate .po file from plugin templates |
| 147 | + env = Environment(extensions=['jinja2.ext.i18n'], |
| 148 | + loader=FileSystemLoader(options.base.plugin)) |
| 149 | + |
| 150 | + msg_strings = [] |
| 151 | + for tfile in ['service_metadata.html', 'record_metadata_dc.html']: |
| 152 | + html_file = options.base.plugin / 'resources/templates' / tfile |
| 153 | + for msg in env.extract_translations(open(html_file).read()): |
| 154 | + if msg[2] not in msg_strings: |
| 155 | + msg_strings.append(msg[2]) |
| 156 | + |
| 157 | + po_file = options.base.plugin / 'locale/en/LC_MESSAGES/templates.po' |
| 158 | + with open(po_file, 'w') as po_file_obj: |
| 159 | + po_file_obj.write( |
| 160 | + '\nmsgid ""\n' |
| 161 | + 'msgstr ""\n' |
| 162 | + '"Project-Id-Version: MetaSearch 0.1-dev\\n"\n' |
| 163 | + '"Report-Msgid-Bugs-To: \\n"\n' |
| 164 | + '"POT-Creation-Date: 2014-02-25 12:58-0500\\n"\n' |
| 165 | + '"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"\n' |
| 166 | + '"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"\n' |
| 167 | + '"Language-Team: LANGUAGE <LL@li.org>\\n"\n' |
| 168 | + '"MIME-Version: 1.0\\n"\n' |
| 169 | + '"Content-Type: text/plain; charset=UTF-8\\n"\n' |
| 170 | + '"Content-Transfer-Encoding: 8bit\\n"\n\n') |
| 171 | + for msg in msg_strings: |
| 172 | + po_file_obj.write('msgid "%s"\nmsgstr ""\n\n' % msg) |
| 173 | + |
| 174 | + # generate docs .po files |
| 175 | + with pushd(options.base.docs): |
| 176 | + sh('make gettext') |
| 177 | + locales_arg = '' |
| 178 | + for lang in os.listdir('locale'): |
| 179 | + locales_arg = '%s -l %s' % (locales_arg, lang) |
| 180 | + sh('sphinx-intl update -p _build/locale %s' % locales_arg) |
| 181 | + |
| 182 | + |
| 183 | +@task |
| 184 | +@needs('build_pro_file') |
| 185 | +def compile_messages(): |
| 186 | + """generate .qm/.po files""" |
| 187 | + |
| 188 | + # generate UI .qm file |
| 189 | + sh('lrelease MetaSearch.pro') |
| 190 | + |
| 191 | + # generate all .mo files |
| 192 | + locales = options.base.plugin / 'locale' |
| 193 | + |
| 194 | + for locale_dir in os.listdir(locales): |
| 195 | + with pushd(locales / locale_dir): |
| 196 | + for filename in os.listdir('LC_MESSAGES'): |
| 197 | + if filename.endswith('.po'): |
| 198 | + with pushd('LC_MESSAGES'): |
| 199 | + sh('msgfmt %s -o %s' % |
| 200 | + (filename, filename.replace('.po', '.mo'))) |
| 201 | + |
| 202 | + # generate docs .mo files |
| 203 | + with pushd(options.base.docs): |
| 204 | + sh('sphinx-intl build') |
| 205 | + |
| 206 | + |
| 207 | +@task |
| 208 | +def install(): |
| 209 | + """install plugin into user QGIS environment""" |
| 210 | + |
| 211 | + plugins_dir = path(USERDIR) / '.qgis2/python/plugins' |
| 212 | + |
| 213 | + if os.path.exists(options.base.install): |
| 214 | + if os.path.islink(options.base.install): |
| 215 | + os.unlink(options.base.install) |
| 216 | + else: |
| 217 | + shutil.rmtree(options.base.install) |
| 218 | + |
| 219 | + if not os.path.exists(plugins_dir): |
| 220 | + raise OSError('The directory %s does not exist.' % plugins_dir) |
| 221 | + if not hasattr(os, 'symlink'): |
| 222 | + shutil.copytree(options.base.plugin, options.base.install) |
| 223 | + elif not os.path.exists(options.base.install): |
| 224 | + os.symlink(options.base.plugin, options.base.install) |
| 225 | + |
| 226 | + |
| 227 | +@task |
| 228 | +def refresh_docs(): |
| 229 | + """Build sphinx docs from scratch""" |
| 230 | + |
| 231 | + get_translations() |
| 232 | + make = sphinx_make() |
| 233 | + with pushd(options.base.docs): |
| 234 | + sh('%s clean' % make) |
| 235 | + sh('sphinx-intl build') |
| 236 | + for lang in os.listdir(options.base.docs / 'locale'): |
| 237 | + builddir = '%s/_build/%s' % (options.base.docs, lang) |
| 238 | + sh('%s -e SPHINXOPTS="-D language=\'%s\'" -e BUILDDIR="%s" html' % |
| 239 | + (make, lang, builddir)) |
| 240 | + |
| 241 | + |
| 242 | +@task |
| 243 | +@needs('refresh_docs') |
| 244 | +def publish_docs(): |
| 245 | + """this script publish Sphinx outputs to github pages""" |
| 246 | + |
| 247 | + tempdir = options.base.tmp / 'tempdocs' |
| 248 | + |
| 249 | + sh('git clone git@github.com:geopython/MetaSearch.git %s' % |
| 250 | + tempdir) |
| 251 | + with pushd(tempdir): |
| 252 | + sh('git checkout gh-pages') |
| 253 | + # copy English to root |
| 254 | + sh('cp -rp %s/docs/_build/en/html/* .' % options.base.home) |
| 255 | + # copy all other languages to their own dir |
| 256 | + for lang in os.listdir(options.base.docs / '_build'): |
| 257 | + if lang != 'en': |
| 258 | + # point all resources to english |
| 259 | + for res in ['_static', '_sources', '_images']: |
| 260 | + sh('rm -fr %s/docs/_build/%s/html/%s' % |
| 261 | + (options.base.home, lang, res)) |
| 262 | + # update .html files to point to English |
| 263 | + for dfile in os.listdir(options.base.docs / |
| 264 | + '_build/%s/html' % lang): |
| 265 | + if dfile.endswith('.html'): |
| 266 | + lfile = options.base.docs / '_build/%s/html/%s' % \ |
| 267 | + (lang, dfile) |
| 268 | + source = open(lfile).read() |
| 269 | + for res in ['_static', '_sources', '_images']: |
| 270 | + source = source.replace(res, '../%s' % res) |
| 271 | + with open(lfile, 'w') as fhl: |
| 272 | + fhl.write(source) |
| 273 | + sh('mkdir -p %s' % lang) |
| 274 | + sh('cp -rp %s/docs/_build/%s/html/* %s' % |
| 275 | + (options.base.home, lang, lang)) |
| 276 | + sh('git add .') |
| 277 | + sh('git commit -am "update live docs [ci skip]"') |
| 278 | + sh('git push origin gh-pages') |
| 279 | + |
| 280 | + tempdir.rmtree() |
| 281 | + |
| 282 | + |
| 283 | +@task |
| 284 | +@needs('setup', 'extract_messages', 'compile_messages') |
| 285 | +def package(): |
| 286 | + """create zip file of plugin""" |
| 287 | + |
| 288 | + package_file = get_package_filename() |
| 289 | + |
| 290 | + if not os.path.exists(options.base.tmp): |
| 291 | + options.base.tmp.mkdir() |
| 292 | + if os.path.exists(package_file): |
| 293 | + os.unlink(package_file) |
| 294 | + with zipfile.ZipFile(package_file, 'w', zipfile.ZIP_DEFLATED) as zipf: |
| 295 | + for root, dirs, files in os.walk(options.base.plugin): |
| 296 | + for file_add in files: |
| 297 | + if file_add.endswith('.pyc'): |
| 298 | + continue |
| 299 | + filepath = os.path.join(root, file_add) |
| 300 | + relpath = os.path.relpath(filepath, |
| 301 | + os.path.join(BASEDIR, 'plugin')) |
| 302 | + zipf.write(filepath, relpath) |
| 303 | + return package_file # return name of created zipfile |
| 304 | + |
| 305 | + |
| 306 | +@task |
| 307 | +@cmdopts([ |
| 308 | + ('user=', 'u', 'OSGeo userid'), |
| 309 | +]) |
| 310 | +def upload(): |
| 311 | + """upload package zipfile to server""" |
| 312 | + |
| 313 | + user = options.get('user', False) |
| 314 | + if not user: |
| 315 | + raise ValueError('OSGeo userid required') |
| 316 | + |
| 317 | + password = getpass.getpass('Enter your password: ') |
| 318 | + if password.strip() == '': |
| 319 | + raise ValueError('password required') |
| 320 | + |
| 321 | + call_task('package') |
| 322 | + |
| 323 | + zipf = get_package_filename() |
| 324 | + |
| 325 | + url = 'http://%s:%s@%s:%d/%s' % (user, password, options.upload.host, |
| 326 | + options.upload.port, |
| 327 | + options.upload.endpoint) |
| 328 | + |
| 329 | + info('Uploading to http://%s/%s' % (options.upload.host, |
| 330 | + options.upload.endpoint)) |
| 331 | + |
| 332 | + server = xmlrpclib.ServerProxy(url, verbose=False) |
| 333 | + |
| 334 | + try: |
| 335 | + with open(zipf) as zfile: |
| 336 | + plugin_id, version_id = \ |
| 337 | + server.plugin.upload(xmlrpclib.Binary(zfile.read())) |
| 338 | + info('Plugin ID: %s', plugin_id) |
| 339 | + info('Version ID: %s', version_id) |
| 340 | + except xmlrpclib.Fault, err: |
| 341 | + error('ERROR: fault error') |
| 342 | + error('Fault code: %d', err.faultCode) |
| 343 | + error('Fault string: %s', err.faultString) |
| 344 | + except xmlrpclib.ProtocolError, err: |
| 345 | + error('Error: Protocol error') |
| 346 | + error("%s : %s", err.errcode, err.errmsg) |
| 347 | + if err.errcode == 403: |
| 348 | + error('Invalid name and password') |
| 349 | + |
| 350 | + |
| 351 | +@task |
| 352 | +def test_default_csw_connections(): |
| 353 | + """test that the default CSW connections work""" |
| 354 | + |
| 355 | + relpath = 'resources/connections-default.xml' |
| 356 | + csw_connections_xml = options.base.plugin / relpath |
| 357 | + |
| 358 | + csws = etree.parse(csw_connections_xml) |
| 359 | + |
| 360 | + for csw in csws.findall('csw'): |
| 361 | + # name = csw.attrib.get('name') |
| 362 | + data = { |
| 363 | + 'service': 'CSW', |
| 364 | + 'version': '2.0.2', |
| 365 | + 'request': 'GetCapabilities' |
| 366 | + } |
| 367 | + values = urlencode(data) |
| 368 | + url = '%s?%s' % (csw.attrib.get('url'), values) |
| 369 | + content = urlopen(url) |
| 370 | + if content.getcode() != 200: |
| 371 | + raise ValueError('Bad HTTP status code') |
| 372 | + csw_xml = etree.fromstring(content.read()) |
| 373 | + tag = '{http://www.opengis.net/cat/csw/2.0.2}Capabilities' |
| 374 | + if csw_xml.tag != tag: |
| 375 | + raise ValueError('root element should be csw:Capabilities') |
| 376 | + |
| 377 | + |
| 378 | +def sphinx_make(): |
| 379 | + """return what command Sphinx is using for make""" |
| 380 | + |
| 381 | + if os.name == 'nt': |
| 382 | + return 'make.bat' |
| 383 | + return 'make' |
| 384 | + |
| 385 | + |
| 386 | +def get_package_filename(): |
| 387 | + """return filepath of plugin zipfile""" |
| 388 | + |
| 389 | + filename = '%s-%s.zip' % (PLUGIN_NAME, options.base.version) |
| 390 | + package_file = '%s/%s' % (options.base.tmp, filename) |
| 391 | + return package_file |
| 392 | + |
| 393 | + |
| 394 | +def get_translations(): |
| 395 | + """get Transifex translations""" |
| 396 | + |
| 397 | + sh('tx pull -a') |
0 commit comments