pb-tool 3.2.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pb_tool/MANIFEST.in +2 -0
- pb_tool/README.txt +3 -0
- pb_tool/pb_tool.py +1061 -0
- pb_tool/templates/basic.tmpl +1 -0
- pb_tool/templates/dialog/__init__.tmpl +35 -0
- pb_tool/templates/dialog/module_name.tmpl +194 -0
- pb_tool/templates/dialog/module_name_dialog.tmpl +42 -0
- pb_tool/templates/dialog/module_name_dialog_base.ui.tmpl +67 -0
- pb_tool/templates/dialog/readme.tmpl +33 -0
- pb_tool/templates/dialog/resources.tmpl +5 -0
- pb_tool/templates/dialog/results.tmpl +42 -0
- pb_tool/templates/dialog.tmpl +1 -0
- pb_tool/templates/icon.png +0 -0
- pb_tool/templates/pb_tool.tmpl +77 -0
- pb_tool/templates/processing/__init__.tmpl +26 -0
- pb_tool/templates/processing/module_name.tmpl +41 -0
- pb_tool/templates/processing/module_name_algorithm.tmpl +80 -0
- pb_tool/templates/processing/module_name_provider.tmpl +39 -0
- pb_tool/templates/processing/readme.tmpl +49 -0
- pb_tool/templates/processing/results.tmpl +46 -0
- pb_tool-3.2.0.dist-info/METADATA +355 -0
- pb_tool-3.2.0.dist-info/RECORD +25 -0
- pb_tool-3.2.0.dist-info/WHEEL +5 -0
- pb_tool-3.2.0.dist-info/entry_points.txt +3 -0
- pb_tool-3.2.0.dist-info/licenses/LICENSE +340 -0
pb_tool/pb_tool.py
ADDED
|
@@ -0,0 +1,1061 @@
|
|
|
1
|
+
"""
|
|
2
|
+
/***************************************************************************
|
|
3
|
+
pb_tool
|
|
4
|
+
A tool for building and deploying QGIS plugins
|
|
5
|
+
-------------------
|
|
6
|
+
begin : 2014-09-24
|
|
7
|
+
copyright : (C) 2014 by GeoApt LLC
|
|
8
|
+
email : gsherman@geoapt.com
|
|
9
|
+
***************************************************************************/
|
|
10
|
+
|
|
11
|
+
/***************************************************************************
|
|
12
|
+
* *
|
|
13
|
+
* This program is free software; you can redistribute it and/or modify *
|
|
14
|
+
* it under the terms of the GNU General Public License as published by *
|
|
15
|
+
* the Free Software Foundation; either version 2 of the License, or *
|
|
16
|
+
* (at your option) any later version. *
|
|
17
|
+
* *
|
|
18
|
+
***************************************************************************/
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__author__ = "gsherman"
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
import subprocess
|
|
26
|
+
import shutil
|
|
27
|
+
import errno
|
|
28
|
+
import fnmatch
|
|
29
|
+
import glob
|
|
30
|
+
import urllib.request
|
|
31
|
+
import urllib.error
|
|
32
|
+
import configparser
|
|
33
|
+
from string import Template
|
|
34
|
+
|
|
35
|
+
import click
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AliasedGroup(click.Group):
|
|
39
|
+
def get_command(self, ctx, cmd_name):
|
|
40
|
+
rv = click.Group.get_command(self, ctx, cmd_name)
|
|
41
|
+
if rv is not None:
|
|
42
|
+
return rv
|
|
43
|
+
matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)]
|
|
44
|
+
if not matches:
|
|
45
|
+
return None
|
|
46
|
+
elif len(matches) == 1:
|
|
47
|
+
return click.Group.get_command(self, ctx, matches[0])
|
|
48
|
+
ctx.fail("Too many matches: %s" % ", ".join(sorted(matches)))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# @click.group()
|
|
52
|
+
@click.command(cls=AliasedGroup)
|
|
53
|
+
def cli():
|
|
54
|
+
"""Simple Python tool to compile and deploy a QGIS plugin.
|
|
55
|
+
For help on a command use --help after the command:
|
|
56
|
+
pb_tool deploy --help.
|
|
57
|
+
|
|
58
|
+
pb_tool requires a configuration file (default: pb_tool.cfg) that
|
|
59
|
+
declares the files and resources used in your plugin. Plugin Builder
|
|
60
|
+
2.6.0 creates a config file when you generate a new plugin template.
|
|
61
|
+
|
|
62
|
+
See http://g-sherman.github.io/plugin_build_tool for for an example config
|
|
63
|
+
file. You can also use the create command to generate a best-guess config
|
|
64
|
+
file for an existing project, then tweak as needed.
|
|
65
|
+
|
|
66
|
+
Bugs and enhancement requests, see:
|
|
67
|
+
https://github.com/g-sherman/plugin_build_tool
|
|
68
|
+
"""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def __version():
|
|
73
|
+
"""return the current version and date"""
|
|
74
|
+
# TODO update this with each release
|
|
75
|
+
return ("2.0.2", "2018-12-26")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_install_files(cfg):
|
|
79
|
+
python_files = cfg.get("files", "python_files").split()
|
|
80
|
+
main_dialog = cfg.get("files", "main_dialog").split()
|
|
81
|
+
extras = cfg.get("files", "extras").split()
|
|
82
|
+
install_files = (
|
|
83
|
+
python_files + main_dialog + compiled_ui(cfg) + compiled_resource(cfg) + extras
|
|
84
|
+
)
|
|
85
|
+
exclusions = cfg.get("files", "excluded_files", fallback="").split()
|
|
86
|
+
if exclusions:
|
|
87
|
+
install_files = [
|
|
88
|
+
f for f in install_files
|
|
89
|
+
if not any(fnmatch.fnmatch(f, pat) for pat in exclusions)
|
|
90
|
+
]
|
|
91
|
+
return install_files
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@cli.command()
|
|
95
|
+
def version():
|
|
96
|
+
"""Return the version of pb_tool and exit"""
|
|
97
|
+
click.echo("{} {}".format(__version()[0], __version()[1]))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@cli.command()
|
|
101
|
+
@click.option(
|
|
102
|
+
"--config_file",
|
|
103
|
+
default="pb_tool.cfg",
|
|
104
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--plugin_path",
|
|
108
|
+
"-p",
|
|
109
|
+
default=None,
|
|
110
|
+
help="Specify the directory where to deploy your plugin if not using the standard location",
|
|
111
|
+
)
|
|
112
|
+
@click.option(
|
|
113
|
+
"--quick",
|
|
114
|
+
"-q",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="Do a quick install without compiling ui, resource, docs, \
|
|
117
|
+
and translation files",
|
|
118
|
+
)
|
|
119
|
+
@click.option(
|
|
120
|
+
"--no-confirm",
|
|
121
|
+
"-y",
|
|
122
|
+
is_flag=True,
|
|
123
|
+
help="Don't ask for confirmation to overwrite existing files",
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"--no-docs",
|
|
127
|
+
"-n",
|
|
128
|
+
is_flag=True,
|
|
129
|
+
help="Skip building the Sphinx help documentation",
|
|
130
|
+
)
|
|
131
|
+
def deploy(config_file, plugin_path, quick, no_confirm, no_docs):
|
|
132
|
+
"""Deploy the plugin to QGIS plugin directory using parameters in pb_tool.cfg"""
|
|
133
|
+
deploy_files(config_file, plugin_path, quick=quick, confirm=not no_confirm, build_help=not no_docs)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def deploy_files(config_file, plugin_path, confirm=True, quick=False, build_help=True):
|
|
137
|
+
"""Deploy the plugin using parameters in pb_tool.cfg"""
|
|
138
|
+
# check for the config file
|
|
139
|
+
if not os.path.exists(config_file):
|
|
140
|
+
click.secho("Configuration file {0} is missing.".format(config_file), fg="red")
|
|
141
|
+
else:
|
|
142
|
+
cfg = get_config(config_file)
|
|
143
|
+
if not plugin_path:
|
|
144
|
+
plugin_path = get_plugin_directory()
|
|
145
|
+
if not plugin_path:
|
|
146
|
+
click.secho("Unable to determine where to deploy your plugin", fg="red")
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
plugin_dir = os.path.join(plugin_path, cfg.get("plugin", "name"))
|
|
150
|
+
|
|
151
|
+
if quick:
|
|
152
|
+
click.secho("Doing quick deployment", fg="green")
|
|
153
|
+
install_files(plugin_dir, cfg)
|
|
154
|
+
click.secho(
|
|
155
|
+
"Quick deployment complete---if you have problems with your"
|
|
156
|
+
" plugin, try doing a full deploy.",
|
|
157
|
+
fg="green",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
if confirm:
|
|
162
|
+
docs_line = " * Build the help docs\n" if build_help else ""
|
|
163
|
+
print("""Deploying will:
|
|
164
|
+
* Remove your currently deployed version
|
|
165
|
+
* Compile the ui and resource files
|
|
166
|
+
{0} * Copy everything to your {1} directory
|
|
167
|
+
""".format(docs_line, plugin_dir))
|
|
168
|
+
|
|
169
|
+
proceed = click.confirm("Proceed?")
|
|
170
|
+
else:
|
|
171
|
+
proceed = True
|
|
172
|
+
|
|
173
|
+
if proceed:
|
|
174
|
+
# clean the deployment
|
|
175
|
+
clean_deployment(False, config_file, plugin_dir)
|
|
176
|
+
click.secho("Deploying to {0}".format(plugin_dir), fg="green")
|
|
177
|
+
# compile to make sure everything is fresh
|
|
178
|
+
click.secho("Compiling to make sure install is clean", fg="green")
|
|
179
|
+
compile_files(cfg)
|
|
180
|
+
if build_help:
|
|
181
|
+
build_docs()
|
|
182
|
+
install_files(plugin_dir, cfg)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def install_files(plugin_dir, cfg):
|
|
186
|
+
errors = []
|
|
187
|
+
install_files = get_install_files(cfg)
|
|
188
|
+
os.makedirs(plugin_dir, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
fail = False
|
|
191
|
+
for file in install_files:
|
|
192
|
+
click.secho("Copying {0}".format(file), fg="magenta", nl=False)
|
|
193
|
+
try:
|
|
194
|
+
dest = os.path.join(plugin_dir, file)
|
|
195
|
+
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
196
|
+
shutil.copy(file, dest)
|
|
197
|
+
print()
|
|
198
|
+
except Exception as oops:
|
|
199
|
+
errors.append("Error copying files: {0}, {1}".format(file, oops.strerror))
|
|
200
|
+
click.echo(click.style(" ----> ERROR", fg="red"))
|
|
201
|
+
fail = True
|
|
202
|
+
extra_dirs = cfg.get("files", "extra_dirs").split()
|
|
203
|
+
# print "EXTRA DIRS: {}".format(extra_dirs)
|
|
204
|
+
for xdir in extra_dirs:
|
|
205
|
+
click.secho(
|
|
206
|
+
"Copying contents of {0} to {1}".format(xdir, plugin_dir),
|
|
207
|
+
fg="magenta",
|
|
208
|
+
nl=False,
|
|
209
|
+
)
|
|
210
|
+
try:
|
|
211
|
+
shutil.copytree(
|
|
212
|
+
xdir, "{0}/{1}".format(plugin_dir, xdir), dirs_exist_ok=True
|
|
213
|
+
)
|
|
214
|
+
print()
|
|
215
|
+
except Exception as oops:
|
|
216
|
+
errors.append("Error copying directory: {0}, {1}".format(xdir, str(oops)))
|
|
217
|
+
click.echo(click.style(" ----> ERROR", fg="red"))
|
|
218
|
+
fail = True
|
|
219
|
+
help_src = cfg.get("help", "dir")
|
|
220
|
+
if os.path.exists(help_src):
|
|
221
|
+
help_target = os.path.join(plugin_dir, cfg.get("help", "target"))
|
|
222
|
+
click.secho(
|
|
223
|
+
"Copying {0} to {1}".format(help_src, help_target), fg="magenta", nl=False
|
|
224
|
+
)
|
|
225
|
+
try:
|
|
226
|
+
shutil.copytree(help_src, help_target, dirs_exist_ok=True)
|
|
227
|
+
print()
|
|
228
|
+
except Exception as oops:
|
|
229
|
+
errors.append("Error copying help files: {0}, {1}".format(help_src, str(oops)))
|
|
230
|
+
click.echo(click.style(" ----> ERROR", fg="red"))
|
|
231
|
+
fail = True
|
|
232
|
+
else:
|
|
233
|
+
click.secho(
|
|
234
|
+
"No help found at {0}, skipping".format(help_src), fg="yellow"
|
|
235
|
+
)
|
|
236
|
+
if fail:
|
|
237
|
+
print("\nERRORS:")
|
|
238
|
+
for error in errors:
|
|
239
|
+
print(error)
|
|
240
|
+
print()
|
|
241
|
+
print(
|
|
242
|
+
"One or more files/directories specified in your config file\n"
|
|
243
|
+
"failed to deploy---make sure they exist or if not needed remove\n"
|
|
244
|
+
"them from the config. To ensure proper deployment, make sure your\n"
|
|
245
|
+
"UI and resource files are compiled. Using dclean to delete the\n"
|
|
246
|
+
"plugin before deploying may also help."
|
|
247
|
+
)
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def clean_deployment(ask_first=True, config="pb_tool.cfg", plugin_dir=None):
|
|
252
|
+
"""Remove the deployed plugin from the .qgis2/python/plugins directory"""
|
|
253
|
+
if not plugin_dir:
|
|
254
|
+
name = get_config(config).get("plugin", "name")
|
|
255
|
+
plugin_dir = os.path.join(get_plugin_directory(), name)
|
|
256
|
+
if ask_first:
|
|
257
|
+
proceed = click.confirm(
|
|
258
|
+
"Delete the deployed plugin from {0}?".format(plugin_dir)
|
|
259
|
+
)
|
|
260
|
+
else:
|
|
261
|
+
proceed = True
|
|
262
|
+
|
|
263
|
+
if proceed:
|
|
264
|
+
click.echo("Removing plugin from {0}".format(plugin_dir))
|
|
265
|
+
try:
|
|
266
|
+
shutil.rmtree(plugin_dir)
|
|
267
|
+
return True
|
|
268
|
+
except OSError as oops:
|
|
269
|
+
print("Plugin was not deleted: {0}".format(oops.strerror))
|
|
270
|
+
else:
|
|
271
|
+
click.echo("Plugin was not deleted")
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@cli.command()
|
|
276
|
+
def clean_docs():
|
|
277
|
+
"""
|
|
278
|
+
Remove the built HTML help files from the build directory
|
|
279
|
+
"""
|
|
280
|
+
if os.path.exists("help"):
|
|
281
|
+
click.echo("Removing built HTML from the help documentation")
|
|
282
|
+
if sys.platform == "win32":
|
|
283
|
+
makeprg = "make.bat"
|
|
284
|
+
else:
|
|
285
|
+
makeprg = "make"
|
|
286
|
+
cwd = os.getcwd()
|
|
287
|
+
os.chdir("help")
|
|
288
|
+
subprocess.check_call([makeprg, "clean"])
|
|
289
|
+
os.chdir(cwd)
|
|
290
|
+
else:
|
|
291
|
+
print("No help directory exists in the current directory")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@cli.command()
|
|
295
|
+
@click.option(
|
|
296
|
+
"--config",
|
|
297
|
+
default="pb_tool.cfg",
|
|
298
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
299
|
+
)
|
|
300
|
+
def dclean(config):
|
|
301
|
+
"""Remove the deployed plugin from the .qgis2/python/plugins directory"""
|
|
302
|
+
clean_deployment(True, config)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@cli.command()
|
|
306
|
+
@click.option(
|
|
307
|
+
"--config",
|
|
308
|
+
default="pb_tool.cfg",
|
|
309
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
310
|
+
)
|
|
311
|
+
def clean(config):
|
|
312
|
+
"""Remove compiled resource and ui files"""
|
|
313
|
+
cfg = get_config(config)
|
|
314
|
+
files = compiled_ui(cfg) + compiled_resource(cfg)
|
|
315
|
+
click.echo("Cleaning resource and ui files")
|
|
316
|
+
for file in files:
|
|
317
|
+
try:
|
|
318
|
+
os.unlink(file)
|
|
319
|
+
print("Deleted: {0}".format(file))
|
|
320
|
+
except OSError as oops:
|
|
321
|
+
print("Couldn't delete {0}: {1}".format(file, oops.strerror))
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@cli.command()
|
|
325
|
+
@click.option(
|
|
326
|
+
"--config",
|
|
327
|
+
default="pb_tool.cfg",
|
|
328
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
329
|
+
)
|
|
330
|
+
def compile(config):
|
|
331
|
+
"""
|
|
332
|
+
Compile the resource and ui files
|
|
333
|
+
"""
|
|
334
|
+
compile_files(get_config(config))
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@cli.command()
|
|
338
|
+
def doc():
|
|
339
|
+
"""Build HTML version of the help files using sphinx"""
|
|
340
|
+
build_docs()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def build_docs():
|
|
344
|
+
"""Build the docs using sphinx"""
|
|
345
|
+
if os.path.exists("help"):
|
|
346
|
+
click.echo("Building the help documentation")
|
|
347
|
+
if sys.platform == "win32":
|
|
348
|
+
makeprg = "make.bat"
|
|
349
|
+
else:
|
|
350
|
+
makeprg = "make"
|
|
351
|
+
env = os.environ.copy()
|
|
352
|
+
env["PYTHON"] = sys.executable
|
|
353
|
+
cwd = os.getcwd()
|
|
354
|
+
os.chdir("help")
|
|
355
|
+
subprocess.check_call([makeprg, "html"], env=env)
|
|
356
|
+
os.chdir(cwd)
|
|
357
|
+
else:
|
|
358
|
+
print("No help directory exists in the current directory")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@cli.command()
|
|
362
|
+
@click.option(
|
|
363
|
+
"--config",
|
|
364
|
+
default="pb_tool.cfg",
|
|
365
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
366
|
+
)
|
|
367
|
+
def translate(config):
|
|
368
|
+
"""Build translations using lrelease. Locales must be specified
|
|
369
|
+
in the config file and the corresponding .ts file must exist in
|
|
370
|
+
the i18n directory of your plugin."""
|
|
371
|
+
possibles = ["lrelease", "lrelease-qt4"]
|
|
372
|
+
for binary in possibles:
|
|
373
|
+
cmd = check_path(binary)
|
|
374
|
+
if cmd:
|
|
375
|
+
break
|
|
376
|
+
if not cmd:
|
|
377
|
+
print(
|
|
378
|
+
"Unable to find the lrelease command. Make sure it is installed"
|
|
379
|
+
" and in your path."
|
|
380
|
+
)
|
|
381
|
+
if sys.platform == "win32":
|
|
382
|
+
print(
|
|
383
|
+
"You can get lrelease by installing"
|
|
384
|
+
" the qt4-devel package in the Libs"
|
|
385
|
+
"\nsection of the OSGeo4W Advanced Install."
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
cfg = get_config(config)
|
|
389
|
+
if check_cfg(cfg, "files", "locales"):
|
|
390
|
+
locales = cfg.get("files", "locales").split()
|
|
391
|
+
if locales:
|
|
392
|
+
for locale in locales:
|
|
393
|
+
name, ext = os.path.splitext(locale)
|
|
394
|
+
if ext != ".ts":
|
|
395
|
+
print("no ts extension")
|
|
396
|
+
locale = name + ".ts"
|
|
397
|
+
print(cmd, locale)
|
|
398
|
+
subprocess.check_call([cmd, os.path.join("i18n", locale)])
|
|
399
|
+
else:
|
|
400
|
+
print("No translations are specified in {0}".format(config))
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@cli.command()
|
|
404
|
+
@click.option(
|
|
405
|
+
"--config",
|
|
406
|
+
default="pb_tool.cfg",
|
|
407
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
408
|
+
)
|
|
409
|
+
@click.option(
|
|
410
|
+
"--quick",
|
|
411
|
+
"-q",
|
|
412
|
+
is_flag=True,
|
|
413
|
+
help="Do a quick packaging without dclean and deploy (plugin must have been previously deployed)",
|
|
414
|
+
)
|
|
415
|
+
def zip(config_file, quick):
|
|
416
|
+
"""Package the plugin into a zip file
|
|
417
|
+
suitable for uploading to the QGIS
|
|
418
|
+
plugin repository"""
|
|
419
|
+
|
|
420
|
+
# check to see if we can find zip or 7z
|
|
421
|
+
use_7z = False
|
|
422
|
+
zip = check_path("zip")
|
|
423
|
+
if not zip:
|
|
424
|
+
# check for 7z
|
|
425
|
+
zip = check_path("7z")
|
|
426
|
+
if not zip:
|
|
427
|
+
click.secho("zip or 7z not found. Unable to package the plugin", fg="red")
|
|
428
|
+
click.secho("Check your path or install a zip program", fg="red")
|
|
429
|
+
return
|
|
430
|
+
else:
|
|
431
|
+
use_7z = True
|
|
432
|
+
click.secho("Found zip: %s" % zip, fg="green")
|
|
433
|
+
|
|
434
|
+
name = get_config(config_file).get("plugin", "name", fallback=None)
|
|
435
|
+
if not quick:
|
|
436
|
+
proceed = click.confirm("This requires a dclean and deploy first. Proceed?")
|
|
437
|
+
if proceed:
|
|
438
|
+
# clean_deployment(False, config)
|
|
439
|
+
deploy_files(config_file, plugin_path=None, confirm=False)
|
|
440
|
+
else:
|
|
441
|
+
# Check to see if the plugin directory exists, otherwise we can't
|
|
442
|
+
# do a quick zip
|
|
443
|
+
if not os.path.exists(os.path.join(get_plugin_directory(), name)):
|
|
444
|
+
# click.secho(
|
|
445
|
+
# "You must deploy the plugin before you can package it using -q",
|
|
446
|
+
# fg='red')
|
|
447
|
+
# proceed = click.confirm(
|
|
448
|
+
# 'Do you want to deploy and proceed with packaging?')
|
|
449
|
+
# if proceed:
|
|
450
|
+
deploy_files(config_file, plugin_path=None, confirm=False)
|
|
451
|
+
proceed = True
|
|
452
|
+
|
|
453
|
+
# confirm = click.confirm(
|
|
454
|
+
# 'Create a packaged plugin ({0}.zip) from the deployed files?'.format(name))
|
|
455
|
+
# confirm = True
|
|
456
|
+
if proceed:
|
|
457
|
+
# delete the zip if it exists
|
|
458
|
+
if os.path.exists("{0}.zip".format(name)):
|
|
459
|
+
os.unlink("{0}.zip".format(name))
|
|
460
|
+
if name:
|
|
461
|
+
cwd = os.getcwd()
|
|
462
|
+
os.chdir(get_plugin_directory())
|
|
463
|
+
# click.secho("Current directory is {}".format(os.getcwd()), fg='magenta')
|
|
464
|
+
if use_7z:
|
|
465
|
+
subprocess.check_call(
|
|
466
|
+
[zip, "a", "-r", os.path.join(cwd, "{0}.zip".format(name)), name]
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
subprocess.check_call(
|
|
470
|
+
[zip, "-r", os.path.join(cwd, "{0}.zip".format(name)), name]
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
print(
|
|
474
|
+
"The {0}.zip archive has been created in the current directory".format(
|
|
475
|
+
name
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
click.echo("Your config file is missing the plugin name (name=parameter)")
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@cli.command()
|
|
483
|
+
@click.option(
|
|
484
|
+
"--config_file",
|
|
485
|
+
default="pb_tool.cfg",
|
|
486
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
487
|
+
)
|
|
488
|
+
def validate(config_file):
|
|
489
|
+
"""
|
|
490
|
+
Check the pb_tool.cfg file for mandatory sections/files
|
|
491
|
+
"""
|
|
492
|
+
valid = True
|
|
493
|
+
cfg = get_config(config_file)
|
|
494
|
+
if not check_cfg(cfg, "plugin", "name"):
|
|
495
|
+
valid = False
|
|
496
|
+
if not check_cfg(cfg, "files", "python_files"):
|
|
497
|
+
valid = False
|
|
498
|
+
if not check_cfg(cfg, "files", "main_dialog"):
|
|
499
|
+
valid = False
|
|
500
|
+
if not check_cfg(cfg, "files", "resource_files"):
|
|
501
|
+
valid = False
|
|
502
|
+
if not check_cfg(cfg, "files", "extras"):
|
|
503
|
+
valid = False
|
|
504
|
+
if not check_cfg(cfg, "help", "dir"):
|
|
505
|
+
valid = False
|
|
506
|
+
if not check_cfg(cfg, "help", "target"):
|
|
507
|
+
valid = False
|
|
508
|
+
|
|
509
|
+
click.secho("Using Python {}".format(sys.version), fg="green")
|
|
510
|
+
if valid:
|
|
511
|
+
click.secho(
|
|
512
|
+
"Your {0} file is valid and contains all mandatory items".format(
|
|
513
|
+
config_file
|
|
514
|
+
),
|
|
515
|
+
fg="green",
|
|
516
|
+
)
|
|
517
|
+
else:
|
|
518
|
+
click.secho("Your {0} file is invalid".format(config_file), fg="red")
|
|
519
|
+
try:
|
|
520
|
+
from qgis.PyQt.QtCore import QStandardPaths, QDir
|
|
521
|
+
|
|
522
|
+
path = QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)[0]
|
|
523
|
+
plugin_path = os.path.join(
|
|
524
|
+
QDir.homePath(), path, "QGIS/QGIS3/profiles/default/python/plugins"
|
|
525
|
+
)
|
|
526
|
+
click.secho("Plugin path: {}".format(plugin_path), fg="green")
|
|
527
|
+
except Exception:
|
|
528
|
+
click.secho(
|
|
529
|
+
"""Unable to determine location of your QGIS Plugin directory.
|
|
530
|
+
Make sure your QGIS environment is setup properly for development and Python
|
|
531
|
+
has access to the qgis.PyQt.QtCore module.""",
|
|
532
|
+
fg="red",
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
zipbin = find_zip()
|
|
536
|
+
a7z = find_7z()
|
|
537
|
+
if zipbin:
|
|
538
|
+
zip_utility = zipbin
|
|
539
|
+
elif a7z:
|
|
540
|
+
zip_utility = a7z
|
|
541
|
+
else:
|
|
542
|
+
zip_utility = None
|
|
543
|
+
if not zip_utility:
|
|
544
|
+
click.secho("zip or 7z not found. Unable to package the plugin", fg="red")
|
|
545
|
+
click.secho("Check your path or install a zip program", fg="red")
|
|
546
|
+
else:
|
|
547
|
+
click.secho("Found suitable zip utility: {}".format(zip_utility), fg="green")
|
|
548
|
+
# check for templates - uncomment next 4 after create function is done
|
|
549
|
+
# print(__file__)
|
|
550
|
+
# print("Module: {}".format (sys.modules['pb_tool']))
|
|
551
|
+
# basic_tmpl = pkgutil.get_data('pb_tool', 'templates/basic.tmpl')
|
|
552
|
+
# print("Read basic template: {}".format(str(basic_tmpl, 'utf-8')))
|
|
553
|
+
|
|
554
|
+
# f = open('pb_tool/templates/basic.tmpl')
|
|
555
|
+
# if f:
|
|
556
|
+
# print("opened basic.tmpl")
|
|
557
|
+
# else:
|
|
558
|
+
# print("unable to find basic.tmpl")
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@cli.command()
|
|
562
|
+
@click.option(
|
|
563
|
+
"--config_file",
|
|
564
|
+
default="pb_tool.cfg",
|
|
565
|
+
help="Name of the config file to use if other than pb_tool.cfg",
|
|
566
|
+
)
|
|
567
|
+
def list(config_file):
|
|
568
|
+
"""List the contents of the configuration file"""
|
|
569
|
+
if os.path.exists(config_file):
|
|
570
|
+
with open(config_file) as cfg:
|
|
571
|
+
for line in cfg:
|
|
572
|
+
print(line[:-1])
|
|
573
|
+
else:
|
|
574
|
+
click.secho(
|
|
575
|
+
"There is no {0} file in the current directory".format(config_file),
|
|
576
|
+
fg="red",
|
|
577
|
+
)
|
|
578
|
+
click.secho("We can't do anything without it", fg="red")
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@cli.command()
|
|
582
|
+
@click.option(
|
|
583
|
+
"--name",
|
|
584
|
+
default="pb_tool.cfg",
|
|
585
|
+
help="Name of the config file to create if other than pb_tool.cfg",
|
|
586
|
+
)
|
|
587
|
+
@click.option(
|
|
588
|
+
"--package",
|
|
589
|
+
default=None,
|
|
590
|
+
help="Name of package (lower case). This will be used as the directory name for deployment",
|
|
591
|
+
)
|
|
592
|
+
def xxconfig(name, package):
|
|
593
|
+
"""
|
|
594
|
+
Create a config file based on source files in the current directory
|
|
595
|
+
"""
|
|
596
|
+
click.secho(
|
|
597
|
+
"Create a config file based on source files in the current directory",
|
|
598
|
+
fg="green",
|
|
599
|
+
)
|
|
600
|
+
if name == "pb_tool.cfg":
|
|
601
|
+
click.secho(
|
|
602
|
+
"This will overwrite any existing pb_tool.cfg in the current directory",
|
|
603
|
+
fg="red",
|
|
604
|
+
)
|
|
605
|
+
proceed = click.confirm("Proceed?")
|
|
606
|
+
if not proceed:
|
|
607
|
+
return
|
|
608
|
+
template = Template(config_template())
|
|
609
|
+
|
|
610
|
+
# get the plugin package name
|
|
611
|
+
if not package:
|
|
612
|
+
cfg_name = click.prompt(
|
|
613
|
+
"Name of package (lower case). This will be used as the directory name for deployment"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
# get the list of python files
|
|
617
|
+
py_files = glob.glob("*.py")
|
|
618
|
+
|
|
619
|
+
# guess the main dialog ui file
|
|
620
|
+
main_dlg = glob.glob("*_dialog_base.ui")
|
|
621
|
+
|
|
622
|
+
# get the other ui files
|
|
623
|
+
other_ui = glob.glob("*.ui")
|
|
624
|
+
# remove the main dialog file
|
|
625
|
+
try:
|
|
626
|
+
for ui in main_dlg:
|
|
627
|
+
other_ui.remove(ui)
|
|
628
|
+
except ValueError:
|
|
629
|
+
# don't care if we didn't find it
|
|
630
|
+
pass
|
|
631
|
+
|
|
632
|
+
# get the resource files (.qrc)
|
|
633
|
+
resources = glob.glob("*.qrc")
|
|
634
|
+
|
|
635
|
+
extras = glob.glob("*.png") + glob.glob("metadata.txt")
|
|
636
|
+
|
|
637
|
+
locale_list = glob.glob("i18n/*.ts")
|
|
638
|
+
locales = []
|
|
639
|
+
for locale in locale_list:
|
|
640
|
+
locales.append(os.path.basename(locale))
|
|
641
|
+
|
|
642
|
+
cfg = template.substitute(
|
|
643
|
+
Name=cfg_name,
|
|
644
|
+
PythonFiles=" ".join(py_files),
|
|
645
|
+
MainDialog=" ".join(main_dlg),
|
|
646
|
+
CompiledUiFiles=" ".join(other_ui),
|
|
647
|
+
Resources=" ".join(resources),
|
|
648
|
+
Extras=" ".join(extras),
|
|
649
|
+
Locales=" ".join(locales),
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
fname = name
|
|
653
|
+
if os.path.exists(fname):
|
|
654
|
+
confirm = click.confirm("{0} exists. Overwrite?".format(name))
|
|
655
|
+
if not confirm:
|
|
656
|
+
fname = click.prompt("Enter a name for the config file:")
|
|
657
|
+
|
|
658
|
+
with open(fname, "w") as f:
|
|
659
|
+
f.write(cfg)
|
|
660
|
+
|
|
661
|
+
print("Created new config file in {0}".format(fname))
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
@cli.command()
|
|
665
|
+
@click.option(
|
|
666
|
+
"--type",
|
|
667
|
+
"plugin_type",
|
|
668
|
+
type=click.Choice(["processing", "dialog"], case_sensitive=False),
|
|
669
|
+
default="processing",
|
|
670
|
+
help="Type of plugin skeleton to create",
|
|
671
|
+
)
|
|
672
|
+
@click.option("--name", default=None, help="Plugin module name (snake_case)")
|
|
673
|
+
@click.option("--class_name", default=None, help="Plugin class name (CamelCase)")
|
|
674
|
+
@click.option("--description", default=None, help="Short plugin description")
|
|
675
|
+
@click.option("--author", default=None, help="Author name")
|
|
676
|
+
@click.option("--email", default=None, help="Author email")
|
|
677
|
+
def create(plugin_type, name, class_name, description, author, email):
|
|
678
|
+
"""Create a new plugin skeleton from a template"""
|
|
679
|
+
from datetime import date
|
|
680
|
+
|
|
681
|
+
name = name or click.prompt("Module name (snake_case, used as directory name)")
|
|
682
|
+
class_name = class_name or click.prompt("Class name (CamelCase)")
|
|
683
|
+
description = description or click.prompt("Description")
|
|
684
|
+
author = author or click.prompt("Author")
|
|
685
|
+
email = email or click.prompt("Email")
|
|
686
|
+
|
|
687
|
+
subs = {
|
|
688
|
+
"TemplateModuleName": name,
|
|
689
|
+
"TemplateClass": class_name,
|
|
690
|
+
"TemplateDescription": description,
|
|
691
|
+
"TemplateAuthor": author,
|
|
692
|
+
"TemplateEmail": email,
|
|
693
|
+
"TemplateYear": str(date.today().year),
|
|
694
|
+
"TemplateBuildDate": date.today().isoformat(),
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
tmpl_dir = os.path.join(os.path.dirname(__file__), "templates", plugin_type)
|
|
698
|
+
if not os.path.exists(tmpl_dir):
|
|
699
|
+
click.secho("No templates found for type '{0}'".format(plugin_type), fg="red")
|
|
700
|
+
return
|
|
701
|
+
|
|
702
|
+
out_dir = name
|
|
703
|
+
if os.path.exists(out_dir):
|
|
704
|
+
if not click.confirm("Directory '{0}' exists. Overwrite?".format(out_dir)):
|
|
705
|
+
return
|
|
706
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
707
|
+
|
|
708
|
+
file_map = {
|
|
709
|
+
"__init__.tmpl": "__init__.py",
|
|
710
|
+
"module_name.tmpl": "{0}.py".format(name),
|
|
711
|
+
"module_name_provider.tmpl": "{0}_provider.py".format(name),
|
|
712
|
+
"module_name_algorithm.tmpl": "{0}_algorithm.py".format(name),
|
|
713
|
+
"module_name_dialog.tmpl": "{0}_dialog.py".format(name),
|
|
714
|
+
"module_name_dialog_base.ui.tmpl": "{0}_dialog_base.ui".format(name),
|
|
715
|
+
"resources.tmpl": "resources.qrc",
|
|
716
|
+
"readme.tmpl": "README.md",
|
|
717
|
+
"results.tmpl": "results.py",
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
created_py = []
|
|
721
|
+
for tmpl_file, out_file in file_map.items():
|
|
722
|
+
tmpl_path = os.path.join(tmpl_dir, tmpl_file)
|
|
723
|
+
if not os.path.exists(tmpl_path):
|
|
724
|
+
continue
|
|
725
|
+
with open(tmpl_path) as f:
|
|
726
|
+
content = Template(f.read()).safe_substitute(subs)
|
|
727
|
+
out_path = os.path.join(out_dir, out_file)
|
|
728
|
+
with open(out_path, "w") as f:
|
|
729
|
+
f.write(content)
|
|
730
|
+
click.secho("Created {0}".format(out_path), fg="green")
|
|
731
|
+
if out_file.endswith(".py"):
|
|
732
|
+
created_py.append(out_file)
|
|
733
|
+
|
|
734
|
+
# generate pb_tool.cfg with the correct python_files already populated
|
|
735
|
+
pb_tool_tmpl_path = os.path.join(os.path.dirname(__file__), "templates", "pb_tool.tmpl")
|
|
736
|
+
if os.path.exists(pb_tool_tmpl_path):
|
|
737
|
+
with open(pb_tool_tmpl_path) as f:
|
|
738
|
+
cfg_content = Template(f.read()).safe_substitute(dict(
|
|
739
|
+
subs,
|
|
740
|
+
TemplateModuleName=name,
|
|
741
|
+
))
|
|
742
|
+
# replace the stub python_files line with the actual generated files
|
|
743
|
+
py_files_line = "python_files: {0}".format(" ".join(created_py))
|
|
744
|
+
cfg_content = cfg_content.replace(
|
|
745
|
+
"python_files: __init__.py {0}.py".format(name),
|
|
746
|
+
py_files_line,
|
|
747
|
+
)
|
|
748
|
+
cfg_path = os.path.join(out_dir, "pb_tool.cfg")
|
|
749
|
+
with open(cfg_path, "w") as f:
|
|
750
|
+
f.write(cfg_content)
|
|
751
|
+
click.secho("Created {0}".format(cfg_path), fg="green")
|
|
752
|
+
|
|
753
|
+
click.secho(
|
|
754
|
+
"\nPlugin skeleton created in '{0}/'. "
|
|
755
|
+
"Add a metadata.txt and run pb_tool deploy to get started.".format(out_dir),
|
|
756
|
+
fg="green",
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
@cli.command()
|
|
761
|
+
def update():
|
|
762
|
+
"""Check for update to pb_tool"""
|
|
763
|
+
try:
|
|
764
|
+
u = urllib.request.urlopen("http://geoapt.net/pb_tool/current_version.txt")
|
|
765
|
+
version = u.read()[:-1]
|
|
766
|
+
click.secho("Latest version is %s" % version, fg="green")
|
|
767
|
+
# convert version numbers to int
|
|
768
|
+
this_version = int(__version()[0].replace(".", ""))
|
|
769
|
+
current_version = int(version.replace(".", ""))
|
|
770
|
+
|
|
771
|
+
if this_version == current_version:
|
|
772
|
+
click.secho("Your version is up to date", fg="green")
|
|
773
|
+
elif current_version > this_version:
|
|
774
|
+
click.secho("You have Version %s" % __version()[0], fg="green")
|
|
775
|
+
click.secho("You can upgrade by running this command:")
|
|
776
|
+
cmd = "pip install --upgrade pb_tool"
|
|
777
|
+
print(" %s" % cmd)
|
|
778
|
+
elif this_version > current_version:
|
|
779
|
+
click.secho("You have development Version %s" % __version()[0], fg="green")
|
|
780
|
+
|
|
781
|
+
except urllib.error.URLError as uoops:
|
|
782
|
+
click.secho("Unable to check for update.")
|
|
783
|
+
click.secho("%s" % uoops.reason)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def check_cfg(cfg, section, name):
|
|
787
|
+
try:
|
|
788
|
+
cfg.get(section, name)
|
|
789
|
+
return True
|
|
790
|
+
except configparser.NoOptionError as oops:
|
|
791
|
+
print(str(oops))
|
|
792
|
+
except configparser.NoSectionError:
|
|
793
|
+
print(
|
|
794
|
+
"Missing section '{0}' when looking for option '{1}'".format(section, name)
|
|
795
|
+
)
|
|
796
|
+
return False
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def get_config(config="pb_tool.cfg"):
|
|
800
|
+
"""
|
|
801
|
+
Read the config file pb_tools.cfg and return it
|
|
802
|
+
"""
|
|
803
|
+
if os.path.exists(config):
|
|
804
|
+
cfg = configparser.ConfigParser()
|
|
805
|
+
cfg.read(config)
|
|
806
|
+
# click.echo(cfg.sections())
|
|
807
|
+
return cfg
|
|
808
|
+
else:
|
|
809
|
+
print("There is no {0} file in the current directory".format(config))
|
|
810
|
+
print("We can't do anything without it")
|
|
811
|
+
sys.exit(1)
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def compiled_ui(cfg):
|
|
815
|
+
# cfg = get_config(config)
|
|
816
|
+
try:
|
|
817
|
+
uis = cfg.get("files", "compiled_ui_files").split()
|
|
818
|
+
compiled = []
|
|
819
|
+
for ui in uis:
|
|
820
|
+
base, ext = os.path.splitext(ui)
|
|
821
|
+
compiled.append("{0}.py".format(base))
|
|
822
|
+
# print("Compiled UI files: {}".format(compiled))
|
|
823
|
+
return compiled
|
|
824
|
+
except configparser.NoSectionError as oops:
|
|
825
|
+
print(str(oops))
|
|
826
|
+
sys.exit(1)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def compiled_resource(cfg):
|
|
830
|
+
# cfg = get_config(config)
|
|
831
|
+
try:
|
|
832
|
+
res_files = cfg.get("files", "resource_files").split()
|
|
833
|
+
compiled = []
|
|
834
|
+
for res in res_files:
|
|
835
|
+
base, ext = os.path.splitext(res)
|
|
836
|
+
compiled.append("{0}.py".format(base))
|
|
837
|
+
# print("Compiled resource files: {}".format(compiled))
|
|
838
|
+
return compiled
|
|
839
|
+
except configparser.NoSectionError as oops:
|
|
840
|
+
print(str(oops))
|
|
841
|
+
sys.exit(1)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def compile_files(cfg):
|
|
845
|
+
# Compile all ui and resource files
|
|
846
|
+
# TODO add changed detection
|
|
847
|
+
# cfg = get_config(config)
|
|
848
|
+
|
|
849
|
+
# determine Qt version and select appropriate uic tool
|
|
850
|
+
if check_path("pyuic6"):
|
|
851
|
+
pyuic = check_path("pyuic6")
|
|
852
|
+
qt_version = 6
|
|
853
|
+
elif check_path("pyuic5"):
|
|
854
|
+
pyuic = check_path("pyuic5")
|
|
855
|
+
qt_version = 5
|
|
856
|
+
else:
|
|
857
|
+
pyuic = None
|
|
858
|
+
qt_version = None
|
|
859
|
+
|
|
860
|
+
if not pyuic:
|
|
861
|
+
print("pyuic5/pyuic6 is not in your path---unable to compile your ui files")
|
|
862
|
+
else:
|
|
863
|
+
print("Using Qt{0} ({1})".format(qt_version, pyuic))
|
|
864
|
+
ui_files = cfg.get("files", "compiled_ui_files").split()
|
|
865
|
+
ui_count = 0
|
|
866
|
+
for ui in ui_files:
|
|
867
|
+
if os.path.exists(ui):
|
|
868
|
+
base, ext = os.path.splitext(ui)
|
|
869
|
+
output = "{0}.py".format(base)
|
|
870
|
+
if file_changed(ui, output):
|
|
871
|
+
print("Compiling {0} to {1}".format(ui, output))
|
|
872
|
+
subprocess.check_call([pyuic, "-o", output, ui])
|
|
873
|
+
ui_count += 1
|
|
874
|
+
else:
|
|
875
|
+
print("Skipping {0} (unchanged)".format(ui))
|
|
876
|
+
else:
|
|
877
|
+
print("{0} does not exist---skipped".format(ui))
|
|
878
|
+
print("Compiled {0} UI files".format(ui_count))
|
|
879
|
+
|
|
880
|
+
# check to see if we have rcc
|
|
881
|
+
rcc = check_path("rcc")
|
|
882
|
+
|
|
883
|
+
if not rcc:
|
|
884
|
+
click.secho(
|
|
885
|
+
"rcc is not in your path---unable to compile your resource file(s)",
|
|
886
|
+
fg="red",
|
|
887
|
+
)
|
|
888
|
+
else:
|
|
889
|
+
res_files = cfg.get("files", "resource_files").split()
|
|
890
|
+
res_count = 0
|
|
891
|
+
for res in res_files:
|
|
892
|
+
if os.path.exists(res):
|
|
893
|
+
base, ext = os.path.splitext(res)
|
|
894
|
+
output = "{0}.py".format(base)
|
|
895
|
+
if file_changed(res, output):
|
|
896
|
+
print("Compiling {0} to {1}".format(res, output))
|
|
897
|
+
cmd = []
|
|
898
|
+
if qt_version == 6:
|
|
899
|
+
cmd += ["rcc", "-g", "python"]
|
|
900
|
+
if qt_version == 5:
|
|
901
|
+
pyrcc5 = check_path("pyrcc5")
|
|
902
|
+
if pyrcc5:
|
|
903
|
+
cmd += [pyrcc5]
|
|
904
|
+
else:
|
|
905
|
+
cmd += [sys.executable, "-m", "PyQt5.pyrcc_main"]
|
|
906
|
+
cmd += ["-o", output, res]
|
|
907
|
+
subprocess.check_call(cmd)
|
|
908
|
+
with open(output, "r") as f:
|
|
909
|
+
content = f.read()
|
|
910
|
+
content = content.replace(
|
|
911
|
+
"from PySide6 import QtCore", "from qgis.PyQt import QtCore"
|
|
912
|
+
)
|
|
913
|
+
with open(output, "w") as f:
|
|
914
|
+
f.write(content)
|
|
915
|
+
res_count += 1
|
|
916
|
+
else:
|
|
917
|
+
print("Skipping {0} (unchanged)".format(res))
|
|
918
|
+
else:
|
|
919
|
+
print("{0} does not exist---skipped".format(res))
|
|
920
|
+
print("Compiled {0} resource files".format(res_count))
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def copy(source, destination):
|
|
924
|
+
"""Copy files recursively.
|
|
925
|
+
|
|
926
|
+
Taken from: http://www.pythoncentral.io/
|
|
927
|
+
how-to-recursively-copy-a-directory-folder-in-python/
|
|
928
|
+
|
|
929
|
+
:param source: Source directory.
|
|
930
|
+
:type source: str
|
|
931
|
+
|
|
932
|
+
:param destination: Destination directory.
|
|
933
|
+
:type destination: str
|
|
934
|
+
|
|
935
|
+
"""
|
|
936
|
+
try:
|
|
937
|
+
shutil.copytree(source, destination, dirs_exist_ok=True)
|
|
938
|
+
except OSError as e:
|
|
939
|
+
# If the error was caused because the source wasn't a directory
|
|
940
|
+
if e.errno == errno.ENOTDIR:
|
|
941
|
+
shutil.copy(source, destination)
|
|
942
|
+
else:
|
|
943
|
+
print("Directory not copied. Error: %s" % e)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def get_plugin_directory():
|
|
947
|
+
home = os.path.expanduser("~")
|
|
948
|
+
qgis4 = os.path.join(".local", "share", "QGIS", "QGIS4", "profiles", "default", "python", "plugins")
|
|
949
|
+
qgis3 = os.path.join(".local", "share", "QGIS", "QGIS3", "profiles", "default", "python", "plugins")
|
|
950
|
+
for candidate in [qgis4, qgis3]:
|
|
951
|
+
path = os.path.join(home, candidate)
|
|
952
|
+
if os.path.exists(path):
|
|
953
|
+
return path
|
|
954
|
+
return os.path.join(home, qgis4)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def config_template():
|
|
958
|
+
"""
|
|
959
|
+
:return: the template for a pb_tool.cfg file
|
|
960
|
+
"""
|
|
961
|
+
template = """# Configuration file for plugin builder tool
|
|
962
|
+
# Sane defaults for your plugin generated by the Plugin Builder are
|
|
963
|
+
# already set below.
|
|
964
|
+
#
|
|
965
|
+
# As you add Python source files and UI files to your plugin, add
|
|
966
|
+
# them to the appropriate [files] section below.
|
|
967
|
+
|
|
968
|
+
[plugin]
|
|
969
|
+
# Name of the plugin. This is the name of the directory that will
|
|
970
|
+
# be created in the QGIS3/QGIS4 python/plugins directory
|
|
971
|
+
name: $Name
|
|
972
|
+
|
|
973
|
+
# Full path to where you want your plugin directory copied. If empty,
|
|
974
|
+
# the QGIS default path will be used. Don't include the plugin name in
|
|
975
|
+
# the path.
|
|
976
|
+
plugin_path:
|
|
977
|
+
|
|
978
|
+
[files]
|
|
979
|
+
# Python files that should be deployed with the plugin
|
|
980
|
+
python_files: $PythonFiles
|
|
981
|
+
|
|
982
|
+
# The main dialog file that is loaded (not compiled)
|
|
983
|
+
main_dialog: $MainDialog
|
|
984
|
+
|
|
985
|
+
# Other ui files for your dialogs (these will be compiled)
|
|
986
|
+
compiled_ui_files: $CompiledUiFiles
|
|
987
|
+
|
|
988
|
+
# Resource file(s) that will be compiled
|
|
989
|
+
resource_files: $Resources
|
|
990
|
+
|
|
991
|
+
# Other files required for the plugin
|
|
992
|
+
extras: $Extras
|
|
993
|
+
|
|
994
|
+
# Files to exclude from deployment (glob patterns, space-separated)
|
|
995
|
+
excluded_files:
|
|
996
|
+
|
|
997
|
+
# Other directories to be deployed with the plugin.
|
|
998
|
+
# These must be subdirectories under the plugin directory
|
|
999
|
+
extra_dirs:
|
|
1000
|
+
|
|
1001
|
+
# ISO code(s) for any locales (translations), separated by spaces.
|
|
1002
|
+
# Corresponding .ts files must exist in the i18n directory
|
|
1003
|
+
locales: $Locales
|
|
1004
|
+
|
|
1005
|
+
[help]
|
|
1006
|
+
# the built help directory that should be deployed with the plugin
|
|
1007
|
+
dir: help/build/html
|
|
1008
|
+
# the name of the directory to target in the deployed plugin
|
|
1009
|
+
target: help
|
|
1010
|
+
"""
|
|
1011
|
+
|
|
1012
|
+
return template
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def check_path(app):
|
|
1016
|
+
"""Adapted from StackExchange:
|
|
1017
|
+
http://stackoverflow.com/questions/377017
|
|
1018
|
+
"""
|
|
1019
|
+
import os
|
|
1020
|
+
|
|
1021
|
+
def is_exe(fpath):
|
|
1022
|
+
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
|
1023
|
+
|
|
1024
|
+
def ext_candidates(fpath):
|
|
1025
|
+
yield fpath
|
|
1026
|
+
for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
|
|
1027
|
+
yield fpath + ext
|
|
1028
|
+
|
|
1029
|
+
fpath, fname = os.path.split(app)
|
|
1030
|
+
if fpath:
|
|
1031
|
+
if is_exe(app):
|
|
1032
|
+
return app
|
|
1033
|
+
else:
|
|
1034
|
+
for path in os.environ["PATH"].split(os.pathsep):
|
|
1035
|
+
exe_file = os.path.join(path, app)
|
|
1036
|
+
for candidate in ext_candidates(exe_file):
|
|
1037
|
+
if is_exe(candidate):
|
|
1038
|
+
return candidate
|
|
1039
|
+
|
|
1040
|
+
return None
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def file_changed(infile, outfile):
|
|
1044
|
+
try:
|
|
1045
|
+
infile_s = os.stat(infile)
|
|
1046
|
+
outfile_s = os.stat(outfile)
|
|
1047
|
+
return infile_s.st_mtime > outfile_s.st_mtime
|
|
1048
|
+
except OSError:
|
|
1049
|
+
return True
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def find_zip():
|
|
1053
|
+
# check to see if we can find zip
|
|
1054
|
+
zip = check_path("zip")
|
|
1055
|
+
return zip
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def find_7z():
|
|
1059
|
+
# check for 7z
|
|
1060
|
+
zip = check_path("7z")
|
|
1061
|
+
return zip
|