utils.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. """
  2. Useful functions to be used in Python scripts.
  3. Usage:
  4. ::
  5. from grass.script import utils as gutils
  6. (C) 2014 by the GRASS Development Team
  7. This program is free software under the GNU General Public
  8. License (>=v2). Read the file COPYING that comes with GRASS
  9. for details.
  10. .. sectionauthor:: Glynn Clements
  11. .. sectionauthor:: Martin Landa <landa.martin gmail.com>
  12. .. sectionauthor:: Anna Petrasova <kratochanna gmail.com>
  13. """
  14. import os
  15. import sys
  16. import shutil
  17. import locale
  18. import shlex
  19. import re
  20. def float_or_dms(s):
  21. """Convert DMS to float.
  22. >>> round(float_or_dms('26:45:30'), 5)
  23. 26.75833
  24. >>> round(float_or_dms('26:0:0.1'), 5)
  25. 26.00003
  26. :param s: DMS value
  27. :return: float value
  28. """
  29. return sum(float(x) / 60 ** n for (n, x) in enumerate(s.split(':')))
  30. def separator(sep):
  31. """Returns separator from G_OPT_F_SEP appropriately converted
  32. to character.
  33. >>> separator('pipe')
  34. '|'
  35. >>> separator('comma')
  36. ','
  37. If the string does not match any of the spearator keywords,
  38. it is returned as is:
  39. >>> separator(', ')
  40. ', '
  41. :param str separator: character or separator keyword
  42. :return: separator character
  43. """
  44. if sep == "pipe":
  45. return "|"
  46. elif sep == "comma":
  47. return ","
  48. elif sep == "space":
  49. return " "
  50. elif sep == "tab" or sep == "\\t":
  51. return "\t"
  52. elif sep == "newline" or sep == "\\n":
  53. return "\n"
  54. return sep
  55. def diff_files(filename_a, filename_b):
  56. """Diffs two text files and returns difference.
  57. :param str filename_a: first file path
  58. :param str filename_b: second file path
  59. :return: list of strings
  60. """
  61. import difflib
  62. differ = difflib.Differ()
  63. fh_a = open(filename_a, 'r')
  64. fh_b = open(filename_b, 'r')
  65. result = list(differ.compare(fh_a.readlines(),
  66. fh_b.readlines()))
  67. return result
  68. def try_remove(path):
  69. """Attempt to remove a file; no exception is generated if the
  70. attempt fails.
  71. :param str path: path to file to remove
  72. """
  73. try:
  74. os.remove(path)
  75. except:
  76. pass
  77. def try_rmdir(path):
  78. """Attempt to remove a directory; no exception is generated if the
  79. attempt fails.
  80. :param str path: path to directory to remove
  81. """
  82. try:
  83. os.rmdir(path)
  84. except:
  85. shutil.rmtree(path, ignore_errors=True)
  86. def basename(path, ext=None):
  87. """Remove leading directory components and an optional extension
  88. from the specified path
  89. :param str path: path
  90. :param str ext: extension
  91. """
  92. name = os.path.basename(path)
  93. if not ext:
  94. return name
  95. fs = name.rsplit('.', 1)
  96. if len(fs) > 1 and fs[1].lower() == ext:
  97. name = fs[0]
  98. return name
  99. class KeyValue(dict):
  100. """A general-purpose key-value store.
  101. KeyValue is a subclass of dict, but also allows entries to be read and
  102. written using attribute syntax. Example:
  103. >>> reg = KeyValue()
  104. >>> reg['north'] = 489
  105. >>> reg.north
  106. 489
  107. >>> reg.south = 205
  108. >>> reg['south']
  109. 205
  110. """
  111. def __getattr__(self, key):
  112. return self[key]
  113. def __setattr__(self, key, value):
  114. self[key] = value
  115. def decode(string):
  116. """Decode string with defualt locale
  117. :param str string: the string to decode
  118. """
  119. enc = locale.getdefaultlocale()[1]
  120. if enc:
  121. if hasattr(string, 'decode'):
  122. return string.decode(enc)
  123. return string
  124. def encode(string):
  125. """Encode string with defualt locale
  126. :param str string: the string to encode
  127. """
  128. enc = locale.getdefaultlocale()[1]
  129. if enc:
  130. if hasattr(string, 'encode'):
  131. return string.encode(enc)
  132. return string
  133. def parse_key_val(s, sep='=', dflt=None, val_type=None, vsep=None):
  134. """Parse a string into a dictionary, where entries are separated
  135. by newlines and the key and value are separated by `sep` (default: `=`)
  136. >>> parse_key_val('min=20\\nmax=50') == {'min': '20', 'max': '50'}
  137. True
  138. >>> parse_key_val('min=20\\nmax=50',
  139. ... val_type=float) == {'min': 20, 'max': 50}
  140. True
  141. :param str s: string to be parsed
  142. :param str sep: key/value separator
  143. :param dflt: default value to be used
  144. :param val_type: value type (None for no cast)
  145. :param vsep: vertical separator (default is Python 'universal newlines' approach)
  146. :return: parsed input (dictionary of keys/values)
  147. """
  148. result = KeyValue()
  149. if not s:
  150. return result
  151. if isinstance(s, bytes):
  152. sep = encode(sep)
  153. vsep = encode(vsep) if vsep else vsep
  154. if vsep:
  155. lines = s.split(vsep)
  156. try:
  157. lines.remove('\n')
  158. except ValueError:
  159. pass
  160. else:
  161. lines = s.splitlines()
  162. for line in lines:
  163. kv = line.split(sep, 1)
  164. k = decode(kv[0].strip())
  165. if len(kv) > 1:
  166. v = decode(kv[1].strip())
  167. else:
  168. v = dflt
  169. if val_type:
  170. result[k] = val_type(v)
  171. else:
  172. result[k] = v
  173. return result
  174. def get_num_suffix(number, max_number):
  175. """Returns formatted number with number of padding zeros
  176. depending on maximum number, used for creating suffix for data series.
  177. Does not include the suffix separator.
  178. :param number: number to be formated as map suffix
  179. :param max_number: maximum number of the series to get number of digits
  180. >>> get_num_suffix(10, 1000)
  181. '0010'
  182. >>> get_num_suffix(10, 10)
  183. '10'
  184. """
  185. return '{number:0{width}d}'.format(width=len(str(max_number)),
  186. number=number)
  187. def split(s):
  188. """!Platform specific shlex.split"""
  189. if sys.version_info >= (2, 6):
  190. return shlex.split(s, posix = (sys.platform != "win32"))
  191. elif sys.platform == "win32":
  192. return shlex.split(s.replace('\\', r'\\'))
  193. else:
  194. return shlex.split(s)
  195. # source:
  196. # http://stackoverflow.com/questions/4836710/
  197. # does-python-have-a-built-in-function-for-string-natural-sort/4836734#4836734
  198. def natural_sort(l):
  199. """Returns sorted strings using natural sort
  200. """
  201. convert = lambda text: int(text) if text.isdigit() else text.lower()
  202. alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
  203. return sorted(l, key=alphanum_key)