~ubuntu-branches/ubuntu/vivid/grass/vivid-proposed

« back to all changes in this revision

Viewing changes to lib/python/script/core.py

  • Committer: Package Import Robot
  • Author(s): Bas Couwenberg
  • Date: 2015-02-20 23:12:08 UTC
  • mfrom: (8.2.6 experimental)
  • Revision ID: package-import@ubuntu.com-20150220231208-1u6qvqm84v430b10
Tags: 7.0.0-1~exp1
* New upstream release.
* Update python-ctypes-ternary.patch to use if/else instead of and/or.
* Drop check4dev patch, rely on upstream check.
* Add build dependency on libpq-dev to grass-dev for libpq-fe.h.
* Drop patches applied upstream, refresh remaining patches.
* Update symlinks for images switched from jpg to png.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Core functions to be used in Python scripts.
 
3
 
 
4
Usage:
 
5
 
 
6
::
 
7
 
 
8
    from grass.script import core as grass
 
9
    grass.parser()
 
10
 
 
11
(C) 2008-2014 by the GRASS Development Team
 
12
This program is free software under the GNU General Public
 
13
License (>=v2). Read the file COPYING that comes with GRASS
 
14
for details.
 
15
 
 
16
.. sectionauthor:: Glynn Clements
 
17
.. sectionauthor:: Martin Landa <landa.martin gmail.com>
 
18
.. sectionauthor:: Michael Barton <michael.barton asu.edu>
 
19
"""
 
20
 
 
21
import os
 
22
import sys
 
23
import types
 
24
import re
 
25
import atexit
 
26
import subprocess
 
27
import shutil
 
28
import codecs
 
29
import types as python_types
 
30
 
 
31
from utils import KeyValue, parse_key_val, basename, encode
 
32
from grass.exceptions import ScriptError, CalledModuleError
 
33
 
 
34
# i18N
 
35
import gettext
 
36
gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"), 'locale'))
 
37
 
 
38
# subprocess wrapper that uses shell on Windows
 
39
 
 
40
 
 
41
class Popen(subprocess.Popen):
 
42
    _builtin_exts = set(['.com', '.exe', '.bat', '.cmd'])
 
43
 
 
44
    @staticmethod
 
45
    def _escape_for_shell(arg):
 
46
        # TODO: what are cmd.exe's parsing rules?
 
47
        return arg
 
48
 
 
49
    def __init__(self, args, **kwargs):
 
50
        if (sys.platform == 'win32'
 
51
            and isinstance(args, list)
 
52
            and not kwargs.get('shell', False)
 
53
            and kwargs.get('executable') is None):
 
54
            cmd = shutil_which(args[0])
 
55
            if cmd is None:
 
56
                raise OSError
 
57
            args = [cmd] + args[1:]
 
58
            name, ext = os.path.splitext(cmd)
 
59
            if ext.lower() not in self._builtin_exts:
 
60
                kwargs['shell'] = True
 
61
                args = [self._escape_for_shell(arg) for arg in args]
 
62
        subprocess.Popen.__init__(self, args, **kwargs)
 
63
 
 
64
PIPE = subprocess.PIPE
 
65
STDOUT = subprocess.STDOUT
 
66
 
 
67
 
 
68
raise_on_error = False  # raise exception instead of calling fatal()
 
69
 
 
70
 
 
71
def call(*args, **kwargs):
 
72
    return Popen(*args, **kwargs).wait()
 
73
 
 
74
# GRASS-oriented interface to subprocess module
 
75
 
 
76
_popen_args = ["bufsize", "executable", "stdin", "stdout", "stderr",
 
77
               "preexec_fn", "close_fds", "cwd", "env",
 
78
               "universal_newlines", "startupinfo", "creationflags"]
 
79
 
 
80
 
 
81
def _make_val(val):
 
82
    if isinstance(val, types.StringType) or \
 
83
            isinstance(val, types.UnicodeType):
 
84
        return val
 
85
    if isinstance(val, types.ListType):
 
86
        return ",".join(map(_make_val, val))
 
87
    if isinstance(val, types.TupleType):
 
88
        return _make_val(list(val))
 
89
    return str(val)
 
90
 
 
91
 
 
92
def get_commands():
 
93
    """Create list of available GRASS commands to use when parsing
 
94
    string from the command line
 
95
 
 
96
    :return: list of commands (set) and directory of scripts (collected
 
97
             by extension - MS Windows only)
 
98
 
 
99
    >>> cmds = list(get_commands()[0])
 
100
    >>> cmds.sort()
 
101
    >>> cmds[:5]
 
102
    ['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate', 'd.erase']
 
103
 
 
104
    """
 
105
    gisbase = os.environ['GISBASE']
 
106
    cmd = list()
 
107
    scripts = {'.py': list()} if sys.platform == 'win32' else {}
 
108
 
 
109
    def scan(gisbase, directory):
 
110
        dir_path = os.path.join(gisbase, directory)
 
111
        if os.path.exists(dir_path):
 
112
            for fname in os.listdir(os.path.join(gisbase, directory)):
 
113
                if scripts:  # win32
 
114
                    name, ext = os.path.splitext(fname)
 
115
                    if ext != '.manifest':
 
116
                        cmd.append(name)
 
117
                    if ext in scripts.keys():
 
118
                        scripts[ext].append(name)
 
119
                else:
 
120
                    cmd.append(fname)
 
121
 
 
122
    for directory in ('bin', 'scripts'):
 
123
        scan(gisbase, directory)
 
124
 
 
125
    # scan gui/scripts/
 
126
    gui_path = os.path.join(gisbase, 'etc', 'gui', 'scripts')
 
127
    if os.path.exists(gui_path):
 
128
        os.environ["PATH"] = os.getenv("PATH") + os.pathsep + gui_path
 
129
        cmd = cmd + os.listdir(gui_path)
 
130
 
 
131
    return set(cmd), scripts
 
132
 
 
133
 
 
134
# replacement for which function from shutil (not available in all versions)
 
135
# from http://hg.python.org/cpython/file/6860263c05b3/Lib/shutil.py#l1068
 
136
# added because of Python scripts running Python scripts on MS Windows
 
137
# see also ticket #2008 which is unrelated but same function was proposed
 
138
def shutil_which(cmd, mode=os.F_OK | os.X_OK, path=None):
 
139
    """Given a command, mode, and a PATH string, return the path which
 
140
    conforms to the given mode on the PATH, or None if there is no such
 
141
    file.
 
142
 
 
143
    `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
 
144
    of os.environ.get("PATH"), or can be overridden with a custom search
 
145
    path.
 
146
 
 
147
    :param cmd: the command
 
148
    :param mode:
 
149
    :param path:
 
150
 
 
151
    """
 
152
    # Check that a given file can be accessed with the correct mode.
 
153
    # Additionally check that `file` is not a directory, as on Windows
 
154
    # directories pass the os.access check.
 
155
    def _access_check(fn, mode):
 
156
        return (os.path.exists(fn) and os.access(fn, mode)
 
157
                and not os.path.isdir(fn))
 
158
 
 
159
    # If we're given a path with a directory part, look it up directly rather
 
160
    # than referring to PATH directories. This includes checking relative to the
 
161
    # current directory, e.g. ./script
 
162
    if os.path.dirname(cmd):
 
163
        if _access_check(cmd, mode):
 
164
            return cmd
 
165
        return None
 
166
 
 
167
    if path is None:
 
168
        path = os.environ.get("PATH", os.defpath)
 
169
    if not path:
 
170
        return None
 
171
    path = path.split(os.pathsep)
 
172
 
 
173
    if sys.platform == "win32":
 
174
        # The current directory takes precedence on Windows.
 
175
        if not os.curdir in path:
 
176
            path.insert(0, os.curdir)
 
177
 
 
178
        # PATHEXT is necessary to check on Windows.
 
179
        pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
 
180
        map(lambda x: x.lower(), pathext) # force lowercase
 
181
        if '.py' not in pathext:          # we assume that PATHEXT contains always '.py'
 
182
            pathext.insert(0, '.py')
 
183
        # See if the given file matches any of the expected path extensions.
 
184
        # This will allow us to short circuit when given "python.exe".
 
185
        # If it does match, only test that one, otherwise we have to try
 
186
        # others.
 
187
        if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
 
188
            files = [cmd]
 
189
        else:
 
190
            files = [cmd + ext for ext in pathext]
 
191
    else:
 
192
        # On other platforms you don't have things like PATHEXT to tell you
 
193
        # what file suffixes are executable, so just pass on cmd as-is.
 
194
        files = [cmd]
 
195
 
 
196
    seen = set()
 
197
    for dir in path:
 
198
        normdir = os.path.normcase(dir)
 
199
        if not normdir in seen:
 
200
            seen.add(normdir)
 
201
            for thefile in files:
 
202
                name = os.path.join(dir, thefile)
 
203
                if _access_check(name, mode):
 
204
                    return name
 
205
    return None
 
206
 
 
207
 
 
208
# Added because of scripts calling scripts on MS Windows.
 
209
# Module name (here cmd) differs from the file name (does not have extension).
 
210
# Additionally, we don't run scripts using system executable mechanism,
 
211
# so we need the full path name.
 
212
# However, scripts are on the PATH and '.PY' in in PATHEXT, so we can use
 
213
# shutil.which to get the full file path. Addons are on PATH too.
 
214
# An alternative to which function call would be to check the script path and
 
215
# addons path. This is proposed improvement for the future.
 
216
# Another alternative is to check some global list of scripts but this list
 
217
# needs to be created first. The question is what is less expensive.
 
218
# Note that getting the full path is only part of the solution,
 
219
# the other part is to use the right Python as an executable and pass the full
 
220
# script path as a parameter.
 
221
# Nevertheless, it is unclear on which places which extensions are added.
 
222
# This function also could skip the check for platform but depends
 
223
# how will be used, this is most general but not most effective.
 
224
def get_real_command(cmd):
 
225
    """Returns the real file commad for a module (cmd)
 
226
 
 
227
    For Python scripts on MS Windows it returns full path to the script
 
228
    and adds a '.py' extension.
 
229
    For other cases it just returns a module (name).
 
230
    So, you can just use this function for all without further check.
 
231
 
 
232
    >>> get_real_command('g.region')
 
233
    'g.region'
 
234
 
 
235
    :param cmd: the command
 
236
    """
 
237
    if sys.platform == 'win32':
 
238
        # we in fact expect pure module name (without extension)
 
239
        # so, lets remove extension
 
240
        if os.path.splitext(cmd)[1] == '.py':
 
241
            cmd = cmd[:-3]
 
242
        full_path = shutil_which(cmd + '.py')
 
243
        if full_path:
 
244
            return full_path
 
245
 
 
246
    return cmd
 
247
 
 
248
 
 
249
def make_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
 
250
                 errors=None, **options):
 
251
    """Return a list of strings suitable for use as the args parameter to
 
252
    Popen() or call(). Example:
 
253
 
 
254
 
 
255
    >>> make_command("g.message", flags = 'w', message = 'this is a warning')
 
256
    ['g.message', '-w', 'message=this is a warning']
 
257
 
 
258
 
 
259
    :param str prog: GRASS module
 
260
    :param str flags: flags to be used (given as a string)
 
261
    :param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
 
262
    :param bool quiet: True to run quietly (<tt>--q</tt>)
 
263
    :param bool verbose: True to run verbosely (<tt>--v</tt>)
 
264
    :param options: module's parameters
 
265
 
 
266
    :return: list of arguments
 
267
    """
 
268
    args = [prog]
 
269
    if overwrite:
 
270
        args.append("--o")
 
271
    if quiet:
 
272
        args.append("--q")
 
273
    if verbose:
 
274
        args.append("--v")
 
275
    if flags:
 
276
        if '-' in flags:
 
277
            raise ScriptError("'-' is not a valid flag")
 
278
        args.append("-%s" % flags)
 
279
    for opt, val in options.iteritems():
 
280
        if opt in _popen_args:
 
281
            continue
 
282
        if val != None:
 
283
            if opt.startswith('_'):
 
284
                opt = opt[1:]
 
285
                warning(_("To run the module <%s> add underscore at the end"
 
286
                    " of the option <%s> to avoid conflict with Python"
 
287
                    " keywords. Underscore at the beginning is"
 
288
                    " depreciated in GRASS GIS 7.0 and will be removed"
 
289
                    " in version 7.1.") % (prog, opt))
 
290
            elif opt.endswith('_'):
 
291
                opt = opt[:-1]
 
292
            args.append("%s=%s" % (opt, _make_val(val)))
 
293
    return args
 
294
 
 
295
 
 
296
def handle_errors(returncode, result, args, kwargs):
 
297
    if returncode == 0:
 
298
        return result
 
299
    handler = kwargs.get('errors', 'raise')
 
300
    if handler.lower() == 'ignore':
 
301
        return result
 
302
    elif handler.lower() == 'status':
 
303
        return returncode
 
304
    elif handler.lower() == 'exit':
 
305
        sys.exit(1)
 
306
    else:
 
307
        # TODO: construction of the whole command is far from perfect
 
308
        args = make_command(*args, **kwargs)
 
309
        raise CalledModuleError(module=None, code=repr(args),
 
310
                                returncode=returncode)
 
311
 
 
312
def start_command(prog, flags="", overwrite=False, quiet=False,
 
313
                  verbose=False, **kwargs):
 
314
    """Returns a Popen object with the command created by make_command.
 
315
    Accepts any of the arguments which Popen() accepts apart from "args"
 
316
    and "shell".
 
317
 
 
318
    >>> p = start_command("g.gisenv", stdout=subprocess.PIPE)
 
319
    >>> print p  # doctest: +ELLIPSIS
 
320
    <...Popen object at 0x...>
 
321
    >>> print p.communicate()[0]  # doctest: +SKIP
 
322
    GISDBASE='/opt/grass-data';
 
323
    LOCATION_NAME='spearfish60';
 
324
    MAPSET='glynn';
 
325
    GRASS_DB_ENCODING='ascii';
 
326
    GUI='text';
 
327
    MONITOR='x0';
 
328
 
 
329
    If the module parameter is the same as Python keyword, add
 
330
    underscore at the end of the parameter. For example, use
 
331
    ``lambda_=1.6`` instead of ``lambda=1.6``.
 
332
 
 
333
    :param str prog: GRASS module
 
334
    :param str flags: flags to be used (given as a string)
 
335
    :param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
 
336
    :param bool quiet: True to run quietly (<tt>--q</tt>)
 
337
    :param bool verbose: True to run verbosely (<tt>--v</tt>)
 
338
    :param kwargs: module's parameters
 
339
 
 
340
    :return: Popen object
 
341
    """
 
342
    options = {}
 
343
    popts = {}
 
344
    for opt, val in kwargs.iteritems():
 
345
        if opt in _popen_args:
 
346
            popts[opt] = val
 
347
        else:
 
348
            if isinstance(val, unicode):
 
349
                val = encode(val)
 
350
            options[opt] = val
 
351
 
 
352
    args = make_command(prog, flags, overwrite, quiet, verbose, **options)
 
353
 
 
354
    if debug_level() > 0:
 
355
        sys.stderr.write("D1/%d: %s.start_command(): %s\n" % (debug_level(),
 
356
                                                              __name__,
 
357
                                                              ' '.join(args)))
 
358
        sys.stderr.flush()
 
359
 
 
360
    return Popen(args, **popts)
 
361
 
 
362
 
 
363
def run_command(*args, **kwargs):
 
364
    """Passes all arguments to start_command(), then waits for the process to
 
365
    complete, returning its exit code. Similar to subprocess.check_call(), but
 
366
    with the make_command() interface.
 
367
 
 
368
    :param list args: list of unnamed arguments (see start_command() for details)
 
369
    :param list kwargs: list of named arguments (see start_command() for details)
 
370
 
 
371
    :return: exit code (0 for success)
 
372
    """
 
373
    ps = start_command(*args, **kwargs)
 
374
    returncode = ps.wait()
 
375
    return handle_errors(returncode, returncode, args, kwargs)
 
376
 
 
377
 
 
378
def pipe_command(*args, **kwargs):
 
379
    """Passes all arguments to start_command(), but also adds
 
380
    "stdout = PIPE". Returns the Popen object.
 
381
 
 
382
    >>> p = pipe_command("g.gisenv")
 
383
    >>> print p  # doctest: +ELLIPSIS
 
384
    <....Popen object at 0x...>
 
385
    >>> print p.communicate()[0]  # doctest: +SKIP
 
386
    GISDBASE='/opt/grass-data';
 
387
    LOCATION_NAME='spearfish60';
 
388
    MAPSET='glynn';
 
389
    GRASS_DB_ENCODING='ascii';
 
390
    GUI='text';
 
391
    MONITOR='x0';
 
392
 
 
393
    :param list args: list of unnamed arguments (see start_command() for details)
 
394
    :param list kwargs: list of named arguments (see start_command() for details)
 
395
 
 
396
    :return: Popen object
 
397
    """
 
398
    kwargs['stdout'] = PIPE
 
399
    return start_command(*args, **kwargs)
 
400
 
 
401
 
 
402
def feed_command(*args, **kwargs):
 
403
    """Passes all arguments to start_command(), but also adds
 
404
    "stdin = PIPE". Returns the Popen object.
 
405
 
 
406
    :param list args: list of unnamed arguments (see start_command() for details)
 
407
    :param list kwargs: list of named arguments (see start_command() for details)
 
408
 
 
409
    :return: Popen object
 
410
    """
 
411
    kwargs['stdin'] = PIPE
 
412
    return start_command(*args, **kwargs)
 
413
 
 
414
 
 
415
def read_command(*args, **kwargs):
 
416
    """Passes all arguments to pipe_command, then waits for the process to
 
417
    complete, returning its stdout (i.e. similar to shell `backticks`).
 
418
 
 
419
    :param list args: list of unnamed arguments (see start_command() for details)
 
420
    :param list kwargs: list of named arguments (see start_command() for details)
 
421
 
 
422
    :return: stdout
 
423
    """
 
424
    process = pipe_command(*args, **kwargs)
 
425
    stdout, unused = process.communicate()
 
426
    returncode = process.poll()
 
427
    return handle_errors(returncode, stdout, args, kwargs)
 
428
 
 
429
 
 
430
def parse_command(*args, **kwargs):
 
431
    """Passes all arguments to read_command, then parses the output
 
432
    by parse_key_val().
 
433
 
 
434
    Parsing function can be optionally given by <em>parse</em> parameter
 
435
    including its arguments, e.g.
 
436
 
 
437
    ::
 
438
 
 
439
        parse_command(..., parse = (grass.parse_key_val, { 'sep' : ':' }))
 
440
 
 
441
    or you can simply define <em>delimiter</em>
 
442
 
 
443
    ::
 
444
 
 
445
        parse_command(..., delimiter = ':')
 
446
 
 
447
    :param args: list of unnamed arguments (see start_command() for details)
 
448
    :param kwargs: list of named arguments (see start_command() for details)
 
449
 
 
450
    :return: parsed module output
 
451
    """
 
452
    parse = None
 
453
    parse_args = {}
 
454
    if 'parse' in kwargs:
 
455
        if type(kwargs['parse']) is types.TupleType:
 
456
            parse = kwargs['parse'][0]
 
457
            parse_args = kwargs['parse'][1]
 
458
        del kwargs['parse']
 
459
 
 
460
    if 'delimiter' in kwargs:
 
461
        parse_args = {'sep': kwargs['delimiter']}
 
462
        del kwargs['delimiter']
 
463
 
 
464
    if not parse:
 
465
        parse = parse_key_val  # use default fn
 
466
 
 
467
    res = read_command(*args, **kwargs)
 
468
 
 
469
    return parse(res, **parse_args)
 
470
 
 
471
 
 
472
def write_command(*args, **kwargs):
 
473
    """Passes all arguments to feed_command, with the string specified
 
474
    by the 'stdin' argument fed to the process' stdin.
 
475
 
 
476
    :param list args: list of unnamed arguments (see start_command() for details)
 
477
    :param list kwargs: list of named arguments (see start_command() for details)
 
478
 
 
479
    :return: return code
 
480
    """
 
481
    stdin = kwargs['stdin']
 
482
    process = feed_command(*args, **kwargs)
 
483
    process.communicate(stdin)
 
484
    returncode = process.poll()
 
485
    return handle_errors(returncode, returncode, args, kwargs)
 
486
 
 
487
 
 
488
def exec_command(prog, flags="", overwrite=False, quiet=False, verbose=False,
 
489
                 env=None, **kwargs):
 
490
    """Interface to os.execvpe(), but with the make_command() interface.
 
491
 
 
492
    :param str prog: GRASS module
 
493
    :param str flags: flags to be used (given as a string)
 
494
    :param bool overwrite: True to enable overwriting the output (<tt>--o</tt>)
 
495
    :param bool quiet: True to run quietly (<tt>--q</tt>)
 
496
    :param bool verbose: True to run verbosely (<tt>--v</tt>)
 
497
    :param env: directory with environmental variables
 
498
    :param list kwargs: module's parameters
 
499
 
 
500
    """
 
501
    args = make_command(prog, flags, overwrite, quiet, verbose, **kwargs)
 
502
 
 
503
    if env is None:
 
504
        env = os.environ
 
505
    os.execvpe(prog, args, env)
 
506
 
 
507
# interface to g.message
 
508
 
 
509
 
 
510
def message(msg, flag=None):
 
511
    """Display a message using `g.message`
 
512
 
 
513
    :param str msg: message to be displayed
 
514
    :param str flag: flags (given as string)
 
515
    """
 
516
    run_command("g.message", flags=flag, message=msg, errors='ignore')
 
517
 
 
518
 
 
519
def debug(msg, debug=1):
 
520
    """Display a debugging message using `g.message -d`
 
521
 
 
522
    :param str msg: debugging message to be displayed
 
523
    :param str debug: debug level (0-5)
 
524
    """
 
525
    if debug_level() >= debug:
 
526
        if sys.platform == "win32":
 
527
            msg = msg.replace('&', '^&')
 
528
 
 
529
        run_command("g.message", flags='d', message=msg, debug=debug)
 
530
 
 
531
def verbose(msg):
 
532
    """Display a verbose message using `g.message -v`
 
533
 
 
534
    :param str msg: verbose message to be displayed
 
535
    """
 
536
    message(msg, flag='v')
 
537
 
 
538
 
 
539
def info(msg):
 
540
    """Display an informational message using `g.message -i`
 
541
 
 
542
    :param str msg: informational message to be displayed
 
543
    """
 
544
    message(msg, flag='i')
 
545
 
 
546
 
 
547
def percent(i, n, s):
 
548
    """Display a progress info message using `g.message -p`
 
549
 
 
550
    ::
 
551
 
 
552
        message(_("Percent complete..."))
 
553
        n = 100
 
554
        for i in range(n):
 
555
            percent(i, n, 1)
 
556
        percent(1, 1, 1)
 
557
 
 
558
    :param int i: current item
 
559
    :param int n: total number of items
 
560
    :param int s: increment size
 
561
    """
 
562
    message("%d %d %d" % (i, n, s), flag='p')
 
563
 
 
564
 
 
565
def warning(msg):
 
566
    """Display a warning message using `g.message -w`
 
567
 
 
568
    :param str msg: warning message to be displayed
 
569
    """
 
570
    message(msg, flag='w')
 
571
 
 
572
 
 
573
def error(msg):
 
574
    """Display an error message using `g.message -e`
 
575
 
 
576
    This function does not end the execution of the program.
 
577
    The right action after the error is up to the caller.
 
578
    For error handling using the standard mechanism use :func:`fatal`.
 
579
 
 
580
    :param str msg: error message to be displayed
 
581
    """
 
582
    message(msg, flag='e')
 
583
 
 
584
 
 
585
def fatal(msg):
 
586
    """Display an error message using `g.message -e`, then abort or raise
 
587
 
 
588
    Raises exception when module global raise_on_error is 'True', abort
 
589
    (calls exit) otherwise.
 
590
    Use func:`set_raise_on_error` to set the behavior.
 
591
 
 
592
    :param str msg: error message to be displayed
 
593
    """
 
594
    global raise_on_error
 
595
    if raise_on_error:
 
596
        raise ScriptError(msg)
 
597
 
 
598
    error(msg)
 
599
    sys.exit(1)
 
600
 
 
601
 
 
602
def set_raise_on_error(raise_exp=True):
 
603
    """Define behaviour on fatal error (fatal() called)
 
604
 
 
605
    :param bool raise_exp: True to raise ScriptError instead of calling
 
606
                           sys.exit(1) in fatal()
 
607
 
 
608
    :return: current status
 
609
    """
 
610
    global raise_on_error
 
611
    tmp_raise = raise_on_error
 
612
    raise_on_error = raise_exp
 
613
    return tmp_raise
 
614
 
 
615
 
 
616
def get_raise_on_error():
 
617
    """Return True if a ScriptError exception is raised instead of calling
 
618
       sys.exit(1) in case a fatal error was invoked with fatal()
 
619
    """
 
620
    global raise_on_error
 
621
    return raise_on_error
 
622
 
 
623
# interface to g.parser
 
624
 
 
625
 
 
626
def _parse_opts(lines):
 
627
    options = {}
 
628
    flags = {}
 
629
    for line in lines:
 
630
        if not line:
 
631
            break
 
632
        try:
 
633
            [var, val] = line.split('=', 1)
 
634
        except:
 
635
            raise SyntaxError("invalid output from g.parser: %s" % line)
 
636
 
 
637
        if var.startswith('flag_'):
 
638
            flags[var[5:]] = bool(int(val))
 
639
        elif var.startswith('opt_'):
 
640
            options[var[4:]] = val
 
641
        elif var in ['GRASS_OVERWRITE', 'GRASS_VERBOSE']:
 
642
            os.environ[var] = val
 
643
        else:
 
644
            raise SyntaxError("invalid output from g.parser: %s" % line)
 
645
 
 
646
    return (options, flags)
 
647
 
 
648
 
 
649
def parser():
 
650
    """Interface to g.parser, intended to be run from the top-level, e.g.:
 
651
 
 
652
    ::
 
653
 
 
654
        if __name__ == "__main__":
 
655
            options, flags = grass.parser()
 
656
            main()
 
657
 
 
658
    Thereafter, the global variables "options" and "flags" will be
 
659
    dictionaries containing option/flag values, keyed by lower-case
 
660
    option/flag names. The values in "options" are strings, those in
 
661
    "flags" are Python booleans.
 
662
    """
 
663
    if not os.getenv("GISBASE"):
 
664
        print >> sys.stderr, "You must be in GRASS GIS to run this program."
 
665
        sys.exit(1)
 
666
 
 
667
    cmdline = [basename(sys.argv[0])]
 
668
    cmdline += ['"' + arg + '"' for arg in sys.argv[1:]]
 
669
    os.environ['CMDLINE'] = ' '.join(cmdline)
 
670
 
 
671
    argv = sys.argv[:]
 
672
    name = argv[0]
 
673
    if not os.path.isabs(name):
 
674
        if os.sep in name or (os.altsep and os.altsep in name):
 
675
            argv[0] = os.path.abspath(name)
 
676
        else:
 
677
            argv[0] = os.path.join(sys.path[0], name)
 
678
 
 
679
    prog = "g.parser.exe" if sys.platform == "win32" else "g.parser"
 
680
    p = subprocess.Popen([prog, '-n'] + argv, stdout=subprocess.PIPE)
 
681
    s = p.communicate()[0]
 
682
    lines = s.split('\0')
 
683
 
 
684
    if not lines or lines[0] != "@ARGS_PARSED@":
 
685
        sys.stdout.write(s)
 
686
        sys.exit(p.returncode)
 
687
 
 
688
    return _parse_opts(lines[1:])
 
689
 
 
690
# interface to g.tempfile
 
691
 
 
692
 
 
693
def tempfile(create=True):
 
694
    """Returns the name of a temporary file, created with g.tempfile.
 
695
 
 
696
    :param bool create: True to create a file
 
697
 
 
698
    :return: path to a tmp file
 
699
    """
 
700
    flags = ''
 
701
    if not create:
 
702
        flags += 'd'
 
703
 
 
704
    return read_command("g.tempfile", flags=flags, pid=os.getpid()).strip()
 
705
 
 
706
 
 
707
def tempdir():
 
708
    """Returns the name of a temporary dir, created with g.tempfile."""
 
709
    tmp = tempfile(create=False)
 
710
    os.mkdir(tmp)
 
711
 
 
712
    return tmp
 
713
 
 
714
 
 
715
def _compare_projection(dic):
 
716
    """Check if projection has some possibility of duplicate names like
 
717
    Universal Transverse Mercator and Universe Transverse Mercator and
 
718
    unify them
 
719
 
 
720
    :param dic: The dictionary containing information about projection
 
721
 
 
722
    :return: The dictionary with the new values if needed
 
723
 
 
724
    """
 
725
    # the lookup variable is a list of list, each list contains all the
 
726
    # possible name for a projection system
 
727
    lookup = [['Universal Transverse Mercator', 'Universe Transverse Mercator']]
 
728
    for lo in lookup:
 
729
        for n in range(len(dic['name'])):
 
730
            if dic['name'][n] in lo:
 
731
                dic['name'][n] = lo[0]
 
732
    return dic
 
733
 
 
734
 
 
735
def _compare_units(dic):
 
736
    """Check if units has some possibility of duplicate names like
 
737
    meter and metre and unify them
 
738
 
 
739
    :param dic: The dictionary containing information about units
 
740
 
 
741
    :return: The dictionary with the new values if needed
 
742
 
 
743
    """
 
744
    # the lookup variable is a list of list, each list contains all the
 
745
    # possible name for a units
 
746
    lookup = [['meter', 'metre'], ['meters', 'metres'], ['kilometer',
 
747
              'kilometre'], ['kilometers', 'kilometres']]
 
748
    for l in lookup:
 
749
        for n in range(len(dic['unit'])):
 
750
            if dic['unit'][n].lower() in l:
 
751
                dic['unit'][n] = l[0]
 
752
        for n in range(len(dic['units'])):
 
753
            if dic['units'][n].lower() in l:
 
754
                dic['units'][n] = l[0]
 
755
    return dic
 
756
 
 
757
 
 
758
def _text_to_key_value_dict(filename, sep=":", val_sep=",", checkproj=False,
 
759
                            checkunits=False):
 
760
    """Convert a key-value text file, where entries are separated by newlines
 
761
    and the key and value are separated by `sep', into a key-value dictionary
 
762
    and discover/use the correct data types (float, int or string) for values.
 
763
 
 
764
    :param str filename: The name or name and path of the text file to convert
 
765
    :param str sep: The character that separates the keys and values, default
 
766
                    is ":"
 
767
    :param str val_sep: The character that separates the values of a single
 
768
                        key, default is ","
 
769
    :param bool checkproj: True if it has to check some information about
 
770
                           projection system
 
771
    :param bool checkproj: True if it has to check some information about units
 
772
 
 
773
    :return: The dictionary
 
774
 
 
775
    A text file with this content:
 
776
    ::
 
777
 
 
778
        a: Hello
 
779
        b: 1.0
 
780
        c: 1,2,3,4,5
 
781
        d : hello,8,0.1
 
782
 
 
783
    Will be represented as this dictionary:
 
784
 
 
785
    ::
 
786
 
 
787
        {'a': ['Hello'], 'c': [1, 2, 3, 4, 5], 'b': [1.0], 'd': ['hello', 8, 0.1]}
 
788
 
 
789
    """
 
790
    text = open(filename, "r").readlines()
 
791
    kvdict = KeyValue()
 
792
 
 
793
    for line in text:
 
794
        if line.find(sep) >= 0:
 
795
            key, value = line.split(sep)
 
796
            key = key.strip()
 
797
            value = value.strip()
 
798
        else:
 
799
            # Jump over empty values
 
800
            continue
 
801
        values = value.split(val_sep)
 
802
        value_list = []
 
803
 
 
804
        for value in values:
 
805
            not_float = False
 
806
            not_int = False
 
807
 
 
808
            # Convert values into correct types
 
809
            # We first try integer then float
 
810
            try:
 
811
                value_converted = int(value)
 
812
            except:
 
813
                not_int = True
 
814
            if not_int:
 
815
                try:
 
816
                    value_converted = float(value)
 
817
                except:
 
818
                    not_float = True
 
819
 
 
820
            if not_int and not_float:
 
821
                value_converted = value.strip()
 
822
 
 
823
            value_list.append(value_converted)
 
824
 
 
825
        kvdict[key] = value_list
 
826
    if checkproj:
 
827
        kvdict = _compare_projection(kvdict)
 
828
    if checkunits:
 
829
        kvdict = _compare_units(kvdict)
 
830
    return kvdict
 
831
 
 
832
 
 
833
def compare_key_value_text_files(filename_a, filename_b, sep=":",
 
834
                                 val_sep=",", precision=0.000001,
 
835
                                 proj=False, units=False):
 
836
    """Compare two key-value text files
 
837
 
 
838
    This method will print a warning in case keys that are present in the first
 
839
    file are not present in the second one.
 
840
    The comparison method tries to convert the values into their native format
 
841
    (float, int or string) to allow correct comparison.
 
842
 
 
843
    An example key-value text file may have this content:
 
844
 
 
845
    ::
 
846
 
 
847
        a: Hello
 
848
        b: 1.0
 
849
        c: 1,2,3,4,5
 
850
        d : hello,8,0.1
 
851
 
 
852
    :param str filename_a: name of the first key-value text file
 
853
    :param str filenmae_b: name of the second key-value text file
 
854
    :param str sep: character that separates the keys and values, default is ":"
 
855
    :param str val_sep: character that separates the values of a single key, default is ","
 
856
    :param double precision: precision with which the floating point values are compared
 
857
    :param bool proj: True if it has to check some information about projection system
 
858
    :param bool units: True if it has to check some information about units
 
859
 
 
860
    :return: True if full or almost identical, False if different
 
861
    """
 
862
    dict_a = _text_to_key_value_dict(filename_a, sep, checkproj=proj,
 
863
                                     checkunits=units)
 
864
    dict_b = _text_to_key_value_dict(filename_b, sep, checkproj=proj,
 
865
                                     checkunits=units)
 
866
 
 
867
    if sorted(dict_a.keys()) != sorted(dict_b.keys()):
 
868
        return False
 
869
 
 
870
    # We compare matching keys
 
871
    for key in dict_a.keys():
 
872
        # Floating point values must be handled separately
 
873
        if isinstance(dict_a[key], float) and isinstance(dict_b[key], float):
 
874
            if abs(dict_a[key] - dict_b[key]) > precision:
 
875
                return False
 
876
        elif isinstance(dict_a[key], float) or isinstance(dict_b[key], float):
 
877
            warning(_("Mixing value types. Will try to compare after "
 
878
                      "integer conversion"))
 
879
            return int(dict_a[key]) == int(dict_b[key])
 
880
        elif key == "+towgs84":
 
881
            # We compare the sum of the entries
 
882
            if abs(sum(dict_a[key]) - sum(dict_b[key])) > precision:
 
883
                return False
 
884
        else:
 
885
            if dict_a[key] != dict_b[key]:
 
886
                return False
 
887
    return True
 
888
 
 
889
# interface to g.gisenv
 
890
 
 
891
 
 
892
def gisenv():
 
893
    """Returns the output from running g.gisenv (with no arguments), as a
 
894
    dictionary. Example:
 
895
 
 
896
    >>> env = gisenv()
 
897
    >>> print env['GISDBASE']  # doctest: +SKIP
 
898
    /opt/grass-data
 
899
 
 
900
    :return: list of GRASS variables
 
901
    """
 
902
    s = read_command("g.gisenv", flags='n')
 
903
    return parse_key_val(s)
 
904
 
 
905
# interface to g.region
 
906
 
 
907
 
 
908
def locn_is_latlong():
 
909
    """Tests if location is lat/long. Value is obtained
 
910
    by checking the "g.region -pu" projection code.
 
911
 
 
912
    :return: True for a lat/long region, False otherwise
 
913
    """
 
914
    s = read_command("g.region", flags='pu')
 
915
    kv = parse_key_val(s, ':')
 
916
    if kv['projection'].split(' ')[0] == '3':
 
917
        return True
 
918
    else:
 
919
        return False
 
920
 
 
921
 
 
922
def region(region3d=False, complete=False):
 
923
    """Returns the output from running "g.region -gu", as a
 
924
    dictionary. Example:
 
925
 
 
926
    :param bool region3d: True to get 3D region
 
927
    :param bool complete:
 
928
 
 
929
    >>> curent_region = region()
 
930
    >>> # obtain n, s, e and w values
 
931
    >>> [curent_region[key] for key in "nsew"]  # doctest: +ELLIPSIS
 
932
    [..., ..., ..., ...]
 
933
    >>> # obtain ns and ew resulutions
 
934
    >>> (curent_region['nsres'], curent_region['ewres'])  # doctest: +ELLIPSIS
 
935
    (..., ...)
 
936
 
 
937
    :return: dictionary of region values
 
938
    """
 
939
    flgs = 'gu'
 
940
    if region3d:
 
941
        flgs += '3'
 
942
    if complete:
 
943
        flgs += 'cep'
 
944
 
 
945
    s = read_command("g.region", flags=flgs)
 
946
    reg = parse_key_val(s, val_type=float)
 
947
    for k in ['rows',  'cols',  'cells',
 
948
              'rows3', 'cols3', 'cells3', 'depths']:
 
949
        if k not in reg:
 
950
            continue
 
951
        reg[k] = int(reg[k])
 
952
 
 
953
    return reg
 
954
 
 
955
 
 
956
def region_env(region3d=False, **kwargs):
 
957
    """Returns region settings as a string which can used as
 
958
    GRASS_REGION environmental variable.
 
959
 
 
960
    If no 'kwargs' are given then the current region is used. Note
 
961
    that this function doesn't modify the current region!
 
962
 
 
963
    See also use_temp_region() for alternative method how to define
 
964
    temporary region used for raster-based computation.
 
965
 
 
966
    :param bool region3d: True to get 3D region
 
967
    :param kwargs: g.region's parameters like 'raster', 'vector' or 'region'
 
968
 
 
969
    ::
 
970
 
 
971
        os.environ['GRASS_REGION'] = grass.region_env(region='detail')
 
972
        grass.mapcalc('map=1', overwrite=True)
 
973
        os.environ.pop('GRASS_REGION')
 
974
 
 
975
    :return: string with region values
 
976
    :return: empty string on error
 
977
    """
 
978
    # read proj/zone from WIND file
 
979
    env = gisenv()
 
980
    windfile = os.path.join(env['GISDBASE'], env['LOCATION_NAME'],
 
981
                            env['MAPSET'], "WIND")
 
982
    fd = open(windfile, "r")
 
983
    grass_region = ''
 
984
    for line in fd.readlines():
 
985
        key, value = map(lambda x: x.strip(), line.split(":", 1))
 
986
        if kwargs and key not in ('proj', 'zone'):
 
987
            continue
 
988
        if not kwargs and not region3d and \
 
989
                key in ('top', 'bottom', 'cols3', 'rows3',
 
990
                        'depths', 'e-w resol3', 'n-s resol3', 't-b resol'):
 
991
            continue
 
992
 
 
993
        grass_region += '%s: %s;' % (key, value)
 
994
 
 
995
    if not kwargs:  # return current region
 
996
        return grass_region
 
997
 
 
998
    # read other values from `g.region -gu`
 
999
    flgs = 'ug'
 
1000
    if region3d:
 
1001
        flgs += '3'
 
1002
 
 
1003
    s = read_command('g.region', flags=flgs, **kwargs)
 
1004
    if not s:
 
1005
        return ''
 
1006
    reg = parse_key_val(s)
 
1007
 
 
1008
    kwdata = [('north',     'n'),
 
1009
              ('south',     's'),
 
1010
              ('east',      'e'),
 
1011
              ('west',      'w'),
 
1012
              ('cols',      'cols'),
 
1013
              ('rows',      'rows'),
 
1014
              ('e-w resol', 'ewres'),
 
1015
              ('n-s resol', 'nsres')]
 
1016
    if region3d:
 
1017
        kwdata += [('top',        't'),
 
1018
                   ('bottom',     'b'),
 
1019
                   ('cols3',      'cols3'),
 
1020
                   ('rows3',      'rows3'),
 
1021
                   ('depths',     'depths'),
 
1022
                   ('e-w resol3', 'ewres3'),
 
1023
                   ('n-s resol3', 'nsres3'),
 
1024
                   ('t-b resol',  'tbres')]
 
1025
 
 
1026
    for wkey, rkey in kwdata:
 
1027
        grass_region += '%s: %s;' % (wkey, reg[rkey])
 
1028
 
 
1029
    return grass_region
 
1030
 
 
1031
 
 
1032
def use_temp_region():
 
1033
    """Copies the current region to a temporary region with "g.region save=",
 
1034
    then sets WIND_OVERRIDE to refer to that region. Installs an atexit
 
1035
    handler to delete the temporary region upon termination.
 
1036
    """
 
1037
    name = "tmp.%s.%d" % (os.path.basename(sys.argv[0]), os.getpid())
 
1038
    run_command("g.region", save=name, overwrite=True)
 
1039
    os.environ['WIND_OVERRIDE'] = name
 
1040
    atexit.register(del_temp_region)
 
1041
 
 
1042
 
 
1043
def del_temp_region():
 
1044
    """Unsets WIND_OVERRIDE and removes any region named by it."""
 
1045
    try:
 
1046
        name = os.environ.pop('WIND_OVERRIDE')
 
1047
        run_command("g.remove", flags='f', quiet=True, type='region', name=name)
 
1048
    except:
 
1049
        pass
 
1050
 
 
1051
# interface to g.findfile
 
1052
 
 
1053
 
 
1054
def find_file(name, element='cell', mapset=None):
 
1055
    """Returns the output from running g.findfile as a
 
1056
    dictionary. Example:
 
1057
 
 
1058
    >>> result = find_file('elevation', element='cell')
 
1059
    >>> print result['fullname']
 
1060
    elevation@PERMANENT
 
1061
    >>> print result['file']  # doctest: +ELLIPSIS
 
1062
    /.../PERMANENT/cell/elevation
 
1063
 
 
1064
 
 
1065
    :param str name: file name
 
1066
    :param str element: element type (default 'cell')
 
1067
    :param str mapset: mapset name (default all mapsets in search path)
 
1068
 
 
1069
    :return: parsed output of g.findfile
 
1070
    """
 
1071
    if element == 'raster' or element == 'rast':
 
1072
        verbose(_('Element type should be "cell" and not "%s"') % element)
 
1073
        element = 'cell'
 
1074
    # g.findfile returns non-zero when file was not found
 
1075
    # se we ignore return code and just focus on stdout
 
1076
    process = start_command('g.findfile', flags='n',
 
1077
                            element=element, file=name, mapset=mapset,
 
1078
                            stdout=PIPE)
 
1079
    stdout = process.communicate()[0]
 
1080
    return parse_key_val(stdout)
 
1081
 
 
1082
# interface to g.list
 
1083
 
 
1084
 
 
1085
def list_strings(type, pattern=None, mapset=None, exclude=None, flag=''):
 
1086
    """List of elements as strings.
 
1087
 
 
1088
    Returns the output from running g.list, as a list of qualified
 
1089
    names.
 
1090
 
 
1091
    :param str type: element type (raster, vector, raster_3d, region, ...)
 
1092
    :param str pattern: pattern string
 
1093
    :param str mapset: mapset name (if not given use search path)
 
1094
    :param str exclude: pattern string to exclude maps from the research
 
1095
    :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
 
1096
                     or '' (glob pattern)
 
1097
 
 
1098
    :return: list of elements
 
1099
    """
 
1100
    if type == 'cell':
 
1101
        verbose(_('Element type should be "raster" and not "%s"') % type)
 
1102
    
 
1103
    result = list()
 
1104
    for line in read_command("g.list",
 
1105
                             quiet=True,
 
1106
                             flags='m' + flag,
 
1107
                             type=type,
 
1108
                             pattern=pattern,
 
1109
                             exclude=exclude,
 
1110
                             mapset=mapset).splitlines():
 
1111
        result.append(line.strip())
 
1112
 
 
1113
    return result
 
1114
 
 
1115
 
 
1116
def list_pairs(type, pattern=None, mapset=None, exclude=None, flag=''):
 
1117
    """List of elements as pairs
 
1118
 
 
1119
    Returns the output from running g.list, as a list of
 
1120
    (name, mapset) pairs
 
1121
 
 
1122
    :param str type: element type (raster, vector, raster_3d, region, ...)
 
1123
    :param str pattern: pattern string
 
1124
    :param str mapset: mapset name (if not given use search path)
 
1125
    :param str exclude: pattern string to exclude maps from the research
 
1126
    :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
 
1127
                     or '' (glob pattern)
 
1128
 
 
1129
    :return: list of elements
 
1130
    """
 
1131
    return [tuple(map.split('@', 1)) for map in list_strings(type, pattern,
 
1132
                                                              mapset, exclude,
 
1133
                                                              flag)]
 
1134
 
 
1135
 
 
1136
def list_grouped(type, pattern=None, check_search_path=True, exclude=None,
 
1137
                 flag=''):
 
1138
    """List of elements grouped by mapsets.
 
1139
 
 
1140
    Returns the output from running g.list, as a dictionary where the
 
1141
    keys are mapset names and the values are lists of maps in that
 
1142
    mapset. Example:
 
1143
 
 
1144
    >>> list_grouped('vect', pattern='*roads*')['PERMANENT']
 
1145
    ['railroads', 'roadsmajor']
 
1146
 
 
1147
    :param str type: element type (raster, vector, raster_3d, region, ...) or list of elements
 
1148
    :param str pattern: pattern string
 
1149
    :param str check_search_path: True to add mapsets for the search path
 
1150
                                  with no found elements
 
1151
    :param str exclude: pattern string to exclude maps from the research
 
1152
    :param str flag: pattern type: 'r' (basic regexp), 'e' (extended regexp),
 
1153
                                    or '' (glob pattern)
 
1154
 
 
1155
    :return: directory of mapsets/elements
 
1156
    """
 
1157
    if isinstance(type, python_types.StringTypes) or len(type) == 1:
 
1158
        types = [type]
 
1159
        store_types = False
 
1160
    else:
 
1161
        types = type
 
1162
        store_types = True
 
1163
        flag += 't'
 
1164
    for i in range(len(types)):
 
1165
        if types[i] == 'cell':
 
1166
            verbose(_('Element type should be "raster" and not "%s"') % types[i])
 
1167
            types[i] = 'raster'
 
1168
    result = {}
 
1169
    if check_search_path:
 
1170
        for mapset in mapsets(search_path=True):
 
1171
            if store_types:
 
1172
                result[mapset] = {}
 
1173
            else:
 
1174
                result[mapset] = []
 
1175
 
 
1176
    mapset = None
 
1177
    for line in read_command("g.list", quiet=True, flags="m" + flag,
 
1178
                             type=types, pattern=pattern, exclude=exclude).splitlines():
 
1179
        try:
 
1180
            name, mapset = line.split('@')
 
1181
        except ValueError:
 
1182
            warning(_("Invalid element '%s'") % line)
 
1183
            continue
 
1184
 
 
1185
        if store_types:
 
1186
            type_, name = name.split('/')
 
1187
            if mapset in result:
 
1188
                if type_ in result[mapset]:
 
1189
                    result[mapset][type_].append(name)
 
1190
                else:
 
1191
                    result[mapset][type_] = [name, ]
 
1192
            else:
 
1193
                result[mapset] = {type_: [name, ]}
 
1194
        else:
 
1195
            if mapset in result:
 
1196
                result[mapset].append(name)
 
1197
            else:
 
1198
                result[mapset] = [name, ]
 
1199
 
 
1200
    return result
 
1201
 
 
1202
# color parsing
 
1203
 
 
1204
named_colors = {
 
1205
    "white":   (1.00, 1.00, 1.00),
 
1206
    "black":   (0.00, 0.00, 0.00),
 
1207
    "red":     (1.00, 0.00, 0.00),
 
1208
    "green":   (0.00, 1.00, 0.00),
 
1209
    "blue":    (0.00, 0.00, 1.00),
 
1210
    "yellow":  (1.00, 1.00, 0.00),
 
1211
    "magenta": (1.00, 0.00, 1.00),
 
1212
    "cyan":    (0.00, 1.00, 1.00),
 
1213
    "aqua":    (0.00, 0.75, 0.75),
 
1214
    "grey":    (0.75, 0.75, 0.75),
 
1215
    "gray":    (0.75, 0.75, 0.75),
 
1216
    "orange":  (1.00, 0.50, 0.00),
 
1217
    "brown":   (0.75, 0.50, 0.25),
 
1218
    "purple":  (0.50, 0.00, 1.00),
 
1219
    "violet":  (0.50, 0.00, 1.00),
 
1220
    "indigo":  (0.00, 0.50, 1.00)}
 
1221
 
 
1222
 
 
1223
def parse_color(val, dflt=None):
 
1224
    """Parses the string "val" as a GRASS colour, which can be either one of
 
1225
    the named colours or an R:G:B tuple e.g. 255:255:255. Returns an
 
1226
    (r,g,b) triple whose components are floating point values between 0
 
1227
    and 1. Example:
 
1228
 
 
1229
    >>> parse_color("red")
 
1230
    (1.0, 0.0, 0.0)
 
1231
    >>> parse_color("255:0:0")
 
1232
    (1.0, 0.0, 0.0)
 
1233
 
 
1234
    :param val: color value
 
1235
    :param dflt: default color value
 
1236
 
 
1237
    :return: tuple RGB
 
1238
    """
 
1239
    if val in named_colors:
 
1240
        return named_colors[val]
 
1241
 
 
1242
    vals = val.split(':')
 
1243
    if len(vals) == 3:
 
1244
        return tuple(float(v) / 255 for v in vals)
 
1245
 
 
1246
    return dflt
 
1247
 
 
1248
# check GRASS_OVERWRITE
 
1249
 
 
1250
 
 
1251
def overwrite():
 
1252
    """Return True if existing files may be overwritten"""
 
1253
    owstr = 'GRASS_OVERWRITE'
 
1254
    return owstr in os.environ and os.environ[owstr] != '0'
 
1255
 
 
1256
# check GRASS_VERBOSE
 
1257
 
 
1258
 
 
1259
def verbosity():
 
1260
    """Return the verbosity level selected by GRASS_VERBOSE"""
 
1261
    vbstr = os.getenv('GRASS_VERBOSE')
 
1262
    if vbstr:
 
1263
        return int(vbstr)
 
1264
    else:
 
1265
        return 2
 
1266
 
 
1267
## various utilities, not specific to GRASS
 
1268
 
 
1269
def find_program(pgm, *args):
 
1270
    """Attempt to run a program, with optional arguments.
 
1271
 
 
1272
    You must call the program in a way that will return a successful
 
1273
    exit code. For GRASS modules this means you need to pass it some
 
1274
    valid CLI option, like "--help". For other programs a common
 
1275
    valid do-little option is usually "--version".
 
1276
 
 
1277
    Example:
 
1278
 
 
1279
    >>> find_program('r.sun', '--help')
 
1280
    True
 
1281
    >>> find_program('ls', '--version')
 
1282
    True
 
1283
 
 
1284
    :param str pgm: program name
 
1285
    :param args: list of arguments
 
1286
 
 
1287
    :return: False if the attempt failed due to a missing executable
 
1288
            or non-zero return code
 
1289
    :return: True otherwise
 
1290
    """
 
1291
    nuldev = file(os.devnull, 'w+')
 
1292
    try:
 
1293
        # TODO: the doc or impl is not correct, any return code is accepted
 
1294
        call([pgm] + list(args), stdin = nuldev, stdout = nuldev, stderr = nuldev)
 
1295
        found = True
 
1296
    except:
 
1297
        found = False
 
1298
    nuldev.close()
 
1299
 
 
1300
    return found
 
1301
 
 
1302
# interface to g.mapsets
 
1303
 
 
1304
 
 
1305
def mapsets(search_path=False):
 
1306
    """List available mapsets
 
1307
 
 
1308
    :param bool search_path: True to list mapsets only in search path
 
1309
 
 
1310
    :return: list of mapsets
 
1311
    """
 
1312
    if search_path:
 
1313
        flags = 'p'
 
1314
    else:
 
1315
        flags = 'l'
 
1316
    mapsets = read_command('g.mapsets',
 
1317
                           flags=flags,
 
1318
                           sep='newline',
 
1319
                           quiet=True)
 
1320
    if not mapsets:
 
1321
        fatal(_("Unable to list mapsets"))
 
1322
 
 
1323
    return mapsets.splitlines()
 
1324
 
 
1325
# interface to `g.proj -c`
 
1326
 
 
1327
 
 
1328
def create_location(dbase, location, epsg=None, proj4=None, filename=None,
 
1329
                    wkt=None, datum=None, datum_trans=None, desc=None,
 
1330
                    overwrite=False):
 
1331
    """Create new location
 
1332
 
 
1333
    Raise ScriptError on error.
 
1334
 
 
1335
    :param str dbase: path to GRASS database
 
1336
    :param str location: location name to create
 
1337
    :param epsg: if given create new location based on EPSG code
 
1338
    :param proj4: if given create new location based on Proj4 definition
 
1339
    :param str filename: if given create new location based on georeferenced file
 
1340
    :param str wkt: if given create new location based on WKT definition
 
1341
                    (path to PRJ file)
 
1342
    :param datum: GRASS format datum code
 
1343
    :param datum_trans: datum transformation parameters (used for epsg and proj4)
 
1344
    :param desc: description of the location (creates MYNAME file)
 
1345
    :param bool overwrite: True to overwrite location if exists(WARNING:
 
1346
                           ALL DATA from existing location ARE DELETED!)
 
1347
    """
 
1348
    gisdbase = None
 
1349
    if epsg or proj4 or filename or wkt:
 
1350
        # FIXME: changing GISDBASE mid-session is not background-job safe
 
1351
        gisdbase = gisenv()['GISDBASE']
 
1352
        run_command('g.gisenv', set='GISDBASE=%s' % dbase)
 
1353
    # create dbase if not exists
 
1354
    if not os.path.exists(dbase):
 
1355
            os.mkdir(dbase)
 
1356
 
 
1357
    # check if location already exists
 
1358
    if os.path.exists(os.path.join(dbase, location)):
 
1359
        if not overwrite:
 
1360
            warning(_("Location <%s> already exists. Operation canceled.") % location)
 
1361
            return
 
1362
        else:
 
1363
            warning(_("Location <%s> already exists and will be overwritten") % location)
 
1364
            shutil.rmtree(os.path.join(dbase, location))
 
1365
 
 
1366
    kwargs = dict()
 
1367
    if datum:
 
1368
        kwargs['datum'] = datum
 
1369
    if datum_trans:
 
1370
        kwargs['datum_trans'] = datum_trans
 
1371
 
 
1372
    if epsg:
 
1373
        ps = pipe_command('g.proj', quiet=True, flags='t', epsg=epsg,
 
1374
                          location=location, stderr=PIPE, **kwargs)
 
1375
    elif proj4:
 
1376
        ps = pipe_command('g.proj', quiet=True, flags='t', proj4=proj4,
 
1377
                          location=location, stderr=PIPE, **kwargs)
 
1378
    elif filename:
 
1379
        ps = pipe_command('g.proj', quiet=True, georef=filename,
 
1380
                          location=location, stderr=PIPE)
 
1381
    elif wkt:
 
1382
        ps = pipe_command('g.proj', quiet=True, wkt=wkt, location=location,
 
1383
                          stderr=PIPE)
 
1384
    else:
 
1385
        _create_location_xy(dbase, location)
 
1386
 
 
1387
    if epsg or proj4 or filename or wkt:
 
1388
        error = ps.communicate()[1]
 
1389
        run_command('g.gisenv', set='GISDBASE=%s' % gisdbase)
 
1390
 
 
1391
        if ps.returncode != 0 and error:
 
1392
            raise ScriptError(repr(error))
 
1393
 
 
1394
    try:
 
1395
        fd = codecs.open(os.path.join(dbase, location, 'PERMANENT', 'MYNAME'),
 
1396
                         encoding='utf-8', mode='w')
 
1397
        if desc:
 
1398
            fd.write(desc + os.linesep)
 
1399
        else:
 
1400
            fd.write(os.linesep)
 
1401
        fd.close()
 
1402
    except OSError as e:
 
1403
        raise ScriptError(repr(e))
 
1404
 
 
1405
 
 
1406
def _create_location_xy(database, location):
 
1407
    """Create unprojected location
 
1408
 
 
1409
    Raise ScriptError on error.
 
1410
 
 
1411
    :param database: GRASS database where to create new location
 
1412
    :param location: location name
 
1413
    """
 
1414
    cur_dir = os.getcwd()
 
1415
    try:
 
1416
        os.chdir(database)
 
1417
        os.mkdir(location)
 
1418
        os.mkdir(os.path.join(location, 'PERMANENT'))
 
1419
 
 
1420
        # create DEFAULT_WIND and WIND files
 
1421
        regioninfo = ['proj:       0',
 
1422
                      'zone:       0',
 
1423
                      'north:      1',
 
1424
                      'south:      0',
 
1425
                      'east:       1',
 
1426
                      'west:       0',
 
1427
                      'cols:       1',
 
1428
                      'rows:       1',
 
1429
                      'e-w resol:  1',
 
1430
                      'n-s resol:  1',
 
1431
                      'top:        1',
 
1432
                      'bottom:     0',
 
1433
                      'cols3:      1',
 
1434
                      'rows3:      1',
 
1435
                      'depths:     1',
 
1436
                      'e-w resol3: 1',
 
1437
                      'n-s resol3: 1',
 
1438
                      't-b resol:  1']
 
1439
 
 
1440
        defwind = open(os.path.join(location,
 
1441
                                    "PERMANENT", "DEFAULT_WIND"), 'w')
 
1442
        for param in regioninfo:
 
1443
            defwind.write(param + '%s' % os.linesep)
 
1444
        defwind.close()
 
1445
 
 
1446
        shutil.copy(os.path.join(location, "PERMANENT", "DEFAULT_WIND"),
 
1447
                    os.path.join(location, "PERMANENT", "WIND"))
 
1448
 
 
1449
        os.chdir(cur_dir)
 
1450
    except OSError as e:
 
1451
        raise ScriptError(repr(e))
 
1452
 
 
1453
# interface to g.version
 
1454
 
 
1455
 
 
1456
def version():
 
1457
    """Get GRASS version as dictionary
 
1458
 
 
1459
    ::
 
1460
 
 
1461
        print version()
 
1462
 
 
1463
        {'proj4': '4.8.0', 'geos': '3.3.5', 'libgis_revision': '52468',
 
1464
         'libgis_date': '2012-07-27 22:53:30 +0200 (Fri, 27 Jul 2012)',
 
1465
         'version': '7.0.svn', 'date': '2012', 'gdal': '2.0dev',
 
1466
         'revision': '53670'}
 
1467
 
 
1468
    """
 
1469
    data = parse_command('g.version', flags='rge', errors='ignore')
 
1470
    for k, v in data.iteritems():
 
1471
        data[k.strip()] = v.replace('"', '').strip()
 
1472
 
 
1473
    return data
 
1474
 
 
1475
# get debug_level
 
1476
_debug_level = None
 
1477
 
 
1478
 
 
1479
def debug_level():
 
1480
    global _debug_level
 
1481
    if _debug_level is not None:
 
1482
        return _debug_level
 
1483
    _debug_level = 0
 
1484
    if find_program('g.gisenv', '--help'):
 
1485
        _debug_level = int(gisenv().get('DEBUG', 0))
 
1486
    return _debug_level
 
1487
 
 
1488
 
 
1489
def legal_name(s):
 
1490
    """Checks if the string contains only allowed characters.
 
1491
 
 
1492
    This is the Python implementation of G_legal_filename() function.
 
1493
 
 
1494
    ..note::
 
1495
 
 
1496
        It is not clear when to use this function.
 
1497
    """
 
1498
    if not s or s[0] == '.':
 
1499
        warning(_("Illegal filename <%s>. Cannot be 'NULL' or start with " \
 
1500
                  "'.'.") % s)
 
1501
        return False
 
1502
 
 
1503
    illegal = [c
 
1504
               for c in s
 
1505
               if c in '/"\'@,=*~' or c <= ' ' or c >= '\177']
 
1506
    if illegal:
 
1507
        illegal = ''.join(sorted(set(illegal)))
 
1508
        warning(_("Illegal filename <%(s)s>. <%(il)s> not allowed.\n") % {
 
1509
        's': s, 'il': illegal})
 
1510
        return False
 
1511
 
 
1512
    return True
 
1513
 
 
1514
 
 
1515
if __name__ == '__main__':
 
1516
    import doctest
 
1517
    doctest.testmod()