source: grass/branches/releasebranch_7_6/lib/python/script/core.py

Last change on this file was 73270, checked in by mmetz, 6 years ago

libpython: revert r73239

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