source: grass/tags/release_20170810_grass_7_2_2RC1/lib/python/script/utils.py

Last change on this file was 69812, checked in by neteler, 8 years ago

Numerous typos fixed (identified with tools/fix_typos.sh) (trunk, r69811)

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-python
File size: 10.7 KB
Line 
1"""
2Useful functions to be used in Python scripts.
3
4Usage:
5
6::
7
8 from grass.script import utils as gutils
9
10(C) 2014-2016 by the GRASS Development Team
11This program is free software under the GNU General Public
12License (>=v2). Read the file COPYING that comes with GRASS
13for details.
14
15.. sectionauthor:: Glynn Clements
16.. sectionauthor:: Martin Landa <landa.martin gmail.com>
17.. sectionauthor:: Anna Petrasova <kratochanna gmail.com>
18"""
19
20import os
21import sys
22import shutil
23import locale
24import shlex
25import re
26
27def float_or_dms(s):
28 """Convert DMS to float.
29
30 >>> round(float_or_dms('26:45:30'), 5)
31 26.75833
32 >>> round(float_or_dms('26:0:0.1'), 5)
33 26.00003
34
35 :param s: DMS value
36
37 :return: float value
38 """
39 return sum(float(x) / 60 ** n for (n, x) in enumerate(s.split(':')))
40
41
42def separator(sep):
43 """Returns separator from G_OPT_F_SEP appropriately converted
44 to character.
45
46 >>> separator('pipe')
47 '|'
48 >>> separator('comma')
49 ','
50
51 If the string does not match any of the separator keywords,
52 it is returned as is:
53
54 >>> separator(', ')
55 ', '
56
57 :param str separator: character or separator keyword
58
59 :return: separator character
60 """
61 if sep == "pipe":
62 return "|"
63 elif sep == "comma":
64 return ","
65 elif sep == "space":
66 return " "
67 elif sep == "tab" or sep == "\\t":
68 return "\t"
69 elif sep == "newline" or sep == "\\n":
70 return "\n"
71 return sep
72
73
74def diff_files(filename_a, filename_b):
75 """Diffs two text files and returns difference.
76
77 :param str filename_a: first file path
78 :param str filename_b: second file path
79
80 :return: list of strings
81 """
82 import difflib
83 differ = difflib.Differ()
84 fh_a = open(filename_a, 'r')
85 fh_b = open(filename_b, 'r')
86 result = list(differ.compare(fh_a.readlines(),
87 fh_b.readlines()))
88 return result
89
90
91def try_remove(path):
92 """Attempt to remove a file; no exception is generated if the
93 attempt fails.
94
95 :param str path: path to file to remove
96 """
97 try:
98 os.remove(path)
99 except:
100 pass
101
102
103def try_rmdir(path):
104 """Attempt to remove a directory; no exception is generated if the
105 attempt fails.
106
107 :param str path: path to directory to remove
108 """
109 try:
110 os.rmdir(path)
111 except:
112 shutil.rmtree(path, ignore_errors=True)
113
114
115def basename(path, ext=None):
116 """Remove leading directory components and an optional extension
117 from the specified path
118
119 :param str path: path
120 :param str ext: extension
121 """
122 name = os.path.basename(path)
123 if not ext:
124 return name
125 fs = name.rsplit('.', 1)
126 if len(fs) > 1 and fs[1].lower() == ext:
127 name = fs[0]
128 return name
129
130
131class KeyValue(dict):
132 """A general-purpose key-value store.
133
134 KeyValue is a subclass of dict, but also allows entries to be read and
135 written using attribute syntax. Example:
136
137 >>> reg = KeyValue()
138 >>> reg['north'] = 489
139 >>> reg.north
140 489
141 >>> reg.south = 205
142 >>> reg['south']
143 205
144 """
145
146 def __getattr__(self, key):
147 return self[key]
148
149 def __setattr__(self, key, value):
150 self[key] = value
151
152
153def decode(bytes_):
154 """Decode bytes with default locale and return (unicode) string
155
156 No-op if parameter is not bytes (assumed unicode string).
157
158 :param bytes bytes_: the bytes to decode
159 """
160 if isinstance(bytes_, bytes):
161 enc = locale.getdefaultlocale()[1]
162 return bytes_.decode(enc) if enc else bytes_.decode()
163 return bytes_
164
165
166def encode(string):
167 """Encode string with default locale and return bytes with that encoding
168
169 No-op if parameter is bytes (assumed already encoded).
170 This ensures garbage in, garbage out.
171
172 :param str string: the string to encode
173 """
174 if isinstance(string, bytes):
175 return string
176 enc = locale.getdefaultlocale()[1]
177 return string.encode(enc) if enc else string.encode()
178
179
180def parse_key_val(s, sep='=', dflt=None, val_type=None, vsep=None):
181 """Parse a string into a dictionary, where entries are separated
182 by newlines and the key and value are separated by `sep` (default: `=`)
183
184 >>> parse_key_val('min=20\\nmax=50') == {'min': '20', 'max': '50'}
185 True
186 >>> parse_key_val('min=20\\nmax=50',
187 ... val_type=float) == {'min': 20, 'max': 50}
188 True
189
190 :param str s: string to be parsed
191 :param str sep: key/value separator
192 :param dflt: default value to be used
193 :param val_type: value type (None for no cast)
194 :param vsep: vertical separator (default is Python 'universal newlines' approach)
195
196 :return: parsed input (dictionary of keys/values)
197 """
198 result = KeyValue()
199
200 if not s:
201 return result
202
203 if isinstance(s, bytes):
204 sep = encode(sep)
205 vsep = encode(vsep) if vsep else vsep
206
207 if vsep:
208 lines = s.split(vsep)
209 try:
210 lines.remove('\n')
211 except ValueError:
212 pass
213 else:
214 lines = s.splitlines()
215
216 for line in lines:
217 kv = line.split(sep, 1)
218 k = decode(kv[0].strip())
219 if len(kv) > 1:
220 v = decode(kv[1].strip())
221 else:
222 v = dflt
223
224 if val_type:
225 result[k] = val_type(v)
226 else:
227 result[k] = v
228
229 return result
230
231
232def get_num_suffix(number, max_number):
233 """Returns formatted number with number of padding zeros
234 depending on maximum number, used for creating suffix for data series.
235 Does not include the suffix separator.
236
237 :param number: number to be formatted as map suffix
238 :param max_number: maximum number of the series to get number of digits
239
240 >>> get_num_suffix(10, 1000)
241 '0010'
242 >>> get_num_suffix(10, 10)
243 '10'
244 """
245 return '{number:0{width}d}'.format(width=len(str(max_number)),
246 number=number)
247
248def split(s):
249 """!Platform specific shlex.split"""
250 if sys.version_info >= (2, 6):
251 return shlex.split(s, posix = (sys.platform != "win32"))
252 elif sys.platform == "win32":
253 return shlex.split(s.replace('\\', r'\\'))
254 else:
255 return shlex.split(s)
256
257
258# source:
259# http://stackoverflow.com/questions/4836710/
260# does-python-have-a-built-in-function-for-string-natural-sort/4836734#4836734
261def natural_sort(l):
262 """Returns sorted strings using natural sort
263 """
264 convert = lambda text: int(text) if text.isdigit() else text.lower()
265 alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
266 return sorted(l, key=alphanum_key)
267
268
269def get_lib_path(modname, libname=None):
270 """Return the path of the libname contained in the module.
271 """
272 from os.path import isdir, join, sep
273 from os import getenv
274
275 if isdir(join(getenv('GISBASE'), 'etc', modname)):
276 path = join(os.getenv('GISBASE'), 'etc', modname)
277 elif getenv('GRASS_ADDON_BASE') and libname and \
278 isdir(join(getenv('GRASS_ADDON_BASE'), 'etc', modname, libname)):
279 path = join(getenv('GRASS_ADDON_BASE'), 'etc', modname)
280 elif getenv('GRASS_ADDON_BASE') and \
281 isdir(join(getenv('GRASS_ADDON_BASE'), 'etc', modname)):
282 path = join(getenv('GRASS_ADDON_BASE'), 'etc', modname)
283 elif getenv('GRASS_ADDON_BASE') and \
284 isdir(join(getenv('GRASS_ADDON_BASE'), modname, modname)):
285 path = join(os.getenv('GRASS_ADDON_BASE'), modname, modname)
286 else:
287 # used by g.extension compilation process
288 cwd = os.getcwd()
289 idx = cwd.find(modname)
290 if idx < 0:
291 return None
292 path = '{cwd}{sep}etc{sep}{modname}'.format(cwd=cwd[:idx+len(modname)],
293 sep=sep,
294 modname=modname)
295 if libname:
296 path += '{pathsep}{cwd}{sep}etc{sep}{modname}{sep}{libname}'.format(
297 cwd=cwd[:idx+len(modname)],
298 sep=sep,
299 modname=modname, libname=libname,
300 pathsep=os.pathsep
301 )
302
303 return path
304
305
306def set_path(modulename, dirname=None, path='.'):
307 """Set sys.path looking in the the local directory GRASS directories.
308
309 :param modulename: string with the name of the GRASS module
310 :param dirname: string with the directory name containing the python
311 libraries, default None
312 :param path: string with the path to reach the dirname locally.
313
314 Example
315 --------
316
317 "set_path" example working locally with the source code of a module
318 (r.green) calling the function with all the parameters. Below it is
319 reported the directory structure on the r.green module.
320
321 ::
322
323 grass_prompt> pwd
324 ~/Download/r.green/r.green.hydro/r.green.hydro.financial
325
326 grass_prompt> tree ../../../r.green
327 ../../../r.green
328 |-- ...
329 |-- libgreen
330 | |-- pyfile1.py
331 | +-- pyfile2.py
332 +-- r.green.hydro
333 |-- Makefile
334 |-- libhydro
335 | |-- pyfile1.py
336 | +-- pyfile2.py
337 |-- r.green.hydro.*
338 +-- r.green.hydro.financial
339 |-- Makefile
340 |-- ...
341 +-- r.green.hydro.financial.py
342
343 21 directories, 125 files
344
345 in the source code the function is called with the following parameters: ::
346
347 set_path('r.green', 'libhydro', '..')
348 set_path('r.green', 'libgreen', os.path.join('..', '..'))
349
350 when we are executing the module: r.green.hydro.financial locally from
351 the command line: ::
352
353 grass_prompt> python r.green.hydro.financial.py --ui
354
355 In this way we are executing the local code even if the module was already
356 installed as grass-addons and it is available in GRASS standards path.
357
358 The function is cheching if the dirname is provided and if the
359 directory exists and it is available using the path
360 provided as third parameter, if yes add the path to sys.path to be
361 importable, otherwise it will check on GRASS GIS standard paths.
362
363 """
364 import sys
365 # TODO: why dirname is checked first - the logic should be revised
366 pathlib = None
367 if dirname:
368 pathlib = os.path.join(path, dirname)
369 if pathlib and os.path.exists(pathlib):
370 # we are running the script from the script directory, therefore
371 # we add the path to sys.path to reach the directory (dirname)
372 sys.path.append(os.path.abspath(path))
373 else:
374 # running from GRASS GIS session
375 path = get_lib_path(modulename, dirname)
376 if path is None:
377 pathname = os.path.join(modulename, dirname) if dirname else modulename
378 raise ImportError("Not able to find the path '%s' directory "
379 "(current dir '%s')." % (pathname, os.getcwd()))
380
381 sys.path.insert(0, path)
Note: See TracBrowser for help on using the repository browser.